Picking a naming convention, and living with it
The separator you choose matters less than the constraints it has to survive. Where each convention belongs, and what breaks when a project mixes them.
A folder holding Final-Report.docx, final report v2.docx and
FINAL_REPORT_v3_FINAL.docx is not a folder with a naming problem. It is a folder where
the decision was never made, only deferred each time somebody saved a new copy. The question
underneath — what should a naming convention be — has a real answer, and it is not a matter of
preference. It comes from the constraints a name has to survive on its way to whoever opens it.
My answer, for anyone who asks at a desk, is to spend your care in the opposite place from where the argument normally happens. The separator gets the argument; the constraints do the damage. Settle on a convention you can stand, apply it per layer, and then watch the rules below, because they are the ones that turn a working project into a build failure on somebody else's machine.
What the conventions are called
The vocabulary is worth getting straight, because these words turn up in style guides, linter
configuration and code review, and using the wrong one sends the conversation sideways.
snake_case joins lowercase words with underscores, as in client_invoices.
kebab-case joins them with hyphens, as in client-invoices.
camelCase drops the separator and capitalises every word after the first, giving
clientInvoices. PascalCase does the same with the first word capitalised
too, giving ClientInvoices. SCREAMING_SNAKE_CASE is snake_case in capitals,
which is where constants and environment variables live. Plain lowercase with no
separator at all is the fallback when a separator would be illegal or invisible, and it is what most
of a downloads folder actually uses.
None of these is better than the others in the abstract. Each is the one that fits a particular layer of a system, and the layer, rather than the person, is what settles it.
Where each convention lives
| Convention | Example | Where it normally lives | What would break |
|---|---|---|---|
| snake_case | client_invoices | Python variables and modules, SQL columns, many config keys | The underscores vanish inside an underlined link, so a URL built from one is often read back with a character missing. |
| kebab-case | client-invoices | CSS class names, HTML attributes, URL slugs | Not a legal identifier in any mainstream language. In Python and JavaScript the hyphen parses as subtraction, so it cannot name a variable or a function. |
| camelCase | clientInvoices | JavaScript variables and functions, Java methods and fields | Case is the only thing separating the words, so a filesystem that ignores case collapses it onto its PascalCase twin. |
| PascalCase | ClientInvoices | Class names, React components, type and interface names | An import spelled clientInvoices still resolves on a case-insensitive machine and fails on a case-sensitive one. |
| SCREAMING_SNAKE_CASE | MAX_RETRY_COUNT | Constants, environment variables | Windows reads environment names case-insensitively and Linux does not, so API_KEY and api_key are one variable on one machine and two on another. |
| lowercase | readme | Package names, archive members, names that have to travel furthest | With no separator the word boundaries are gone: finalreportv3final has to be read letter by letter, and two people will break it in different places. |
Every hazard in the last column comes from the environment reading the name, not from the name being ugly. That is the pattern worth taking from this table: the question is never which convention looks best, but which one the layer you are naming in can actually parse, and what happens to the name when it crosses a boundary into a system with different rules.
The layer decides the casing, not taste
A name has to be legal where it is written, and legality is decided by the grammar of whatever does
the reading. The hyphen is the clearest case, because in nearly every programming language it already
means something: subtraction. Write client-invoices = 5 in Python, or
let client-invoices = 5 in JavaScript, and you do not get a variable with a hyphen in
it. You get a syntax error, because the parser read a subtraction and then found nothing legal on
the left of the equals sign. The same name has to be written client_invoices or
clientInvoices, and no amount of preference changes that.
A name that starts with a digit fails for a different reason. 2fa_token is illegal as an
identifier in Python, JavaScript, Java and C, because the lexer reads the leading digits as the start
of a number before it ever reaches the letters. You can start a filename with a digit, and you can
start a folder name with one, but you cannot start a variable with one, which is why codebases that
also contain a file called 2fa-notes.md end up with variables spelled
two_fa_token or tfa_token inside them.
A space is the third case, and it is the one that catches people outside code entirely. A space is
perfectly legal in a filename, which is why final report v2.docx saves without
complaint. Everywhere else the name is used, the space has to be escaped or quoted. It becomes
%20 in a URL, and on a command line it needs quoting, or the shell reads it as the end
of the argument and goes looking for a file called final. So one document is
final report v2.docx on disk, final%20report%20v2.docx inside a link, and
"final report v2.docx" in a terminal.
This is why a single project can hold kebab-case URLs, snake_case database columns and camelCase variables at the same time, and why that is correct rather than inconsistent. Each of those names is written in a different language with different rules. The URL is a string in a document, the column is an identifier in SQL, the variable is an identifier in JavaScript. A project that insisted on one convention across all of them would have to pick the strictest and give up readability everywhere else.
The case-insensitive filesystem trap
Windows and macOS, out of the box, do not distinguish Report.txt from
report.txt. They are the same file, and writing the second overwrites the first. Linux
distinguishes them, and will hold both in one folder without comment. That difference is invisible
while you work and expensive the moment a project leaves the laptop it was written on, because it
means a name can be correct on the machine that created it and wrong on the machine that runs it.
The failure is quiet. A file saved as Config.js and imported as ./config.js
builds without complaint on a case-insensitive laptop and then fails on a Linux build machine with
Module not found: Can't resolve './config.js'. The message points at the import, which
is spelled exactly as the author sees it, and there is nothing wrong with the code at all. The name
was never unambiguous in the first place, and one of the two systems was always going to disagree.
Renames carry the same trap. On a case-insensitive filesystem, renaming Report.txt to
report.txt looks like it did nothing, because from the filesystem's point of view it
did nothing. Version control is where this bites hardest, since a tool records the name it is told
about, and a case-only rename can sit in the working tree as a change that never gets staged. If you
need to change only the case of a tracked file, rename it through a temporary name in between, and
check what your tool actually recorded before you commit.
The safe rule costs nothing and covers every instance of this: treat every name as though case
mattered, and never let two names in one project differ only in case. README and
readme sitting in the same folder is not a style question or a matter of taste. It is a
bug that has not deployed yet.
Where the real damage is
The damage is never the separator you chose. It is the mixture. A folder holding
Client-Invoices, client_invoices and clientInvoices gives you
a separate spelling to remember for each one, and it gives you no way to sort or search reliably,
because any search term you type matches one of them and misses the rest. The misses are the nasty
part: a search that returns results looks like a search that worked.
This is worth fixing once, in a single pass, rather than a file at a time as you happen to touch them. Batch conversion is the right shape for it — put the whole list of names in, convert the list, and rename in one go while the original list is still in front of you to check against.
Where the tools fit. The Case Converter takes a block of names and gives them back in snake_case, kebab-case, camelCase, PascalCase, SCREAMING_SNAKE_CASE or plain lowercase, which is the single-pass rename described above. The Word Counter covers the other half of the job: paste the list before and after the conversion and compare the totals, because a rename that quietly dropped one name is the failure that costs you an afternoon of hunting.
Names that survive anywhere
Whatever convention you settle on, some characters are worth avoiding outright, because they work on your machine and fail somewhere further down the line. Spaces are the first: legal on disk, quoted or escaped in every other context a name is used in. Accented characters and emoji are the second. They survive on most systems and break in some archives, some URLs and some toolchains, because the bytes get re-encoded somewhere in the middle and the name arrives different from how it left. Non-ASCII names also case-fold differently in different locales, which reintroduces the case problem above in a form that is far harder to see.
Then the small rules, which cost nothing to follow. No name should start or end with a space, and none should start or end with a dot. A trailing space is invisible in a file browser and makes the name a different name from the one without it, so it will not match a search, including the search you are using to find it. A leading dot hides the file on Unix-like systems, which is occasionally what you want and usually not. A trailing dot is ordinary on Linux and silently stripped by Windows, so the same name becomes two different names depending on which system created it.
The date goes first
Dates deserve their own rule, and it is the most checkable one on this page. If a folder of files
should sort by date, the date goes at the front and is written biggest unit first, on ISO 8601:
2026-06-02-invoice-acme.pdf. Text sorting compares characters from the left, so that
works, because the year sorts before the month, which sorts before the day, which is the order you
wanted anyway.
The format most people reach for, 02/06/2026, sorts by day of month instead.
01/04/2026 lands before 02/03/2026 in a sorted list, even though April is a
month later than March, because the comparison stops at the second character. It is also ambiguous on
sight: that same string is the second of June to most of the world and the sixth of February in the
United States. Nothing about the dates is wrong. The order they are written in is.
Versioning, honestly
A folder with final, final2 and FINAL-final in it does have a
naming convention. It is a bad one, arrived at by accident. The trouble is not the ugliness of it;
it is that the order is not recoverable from the names. final2 is later than
final, but FINAL-final could be later than both or a return to the first
one, and nothing in the string says which. Anyone opening that folder has to guess, and the guess
becomes permanent once the person who made the files has moved on.
Both fixes work the same way: make the name sortable as text. A date does it, for the reason above —
2026-06-02-report.docx lands in the right place in a list without anybody thinking about
it. A padded version number does it too. v01, v02 and v10 sort
in the order you expect, because every number is the same width. v1, v2 and
v10 do not: as text, v10 comes between them, since the comparison stops at
the first character that differs and 0 sorts before 2. Padding costs one
leading zero per number up to ninety-nine, and it buys you a folder that can be sorted by name and
trusted.
What to do in a project
Choose the convention per layer, once, and write it down somewhere one line long. That line is the whole artefact: Python modules snake_case, CSS classes kebab-case, environment variables SCREAMING_SNAKE, files lowercase with hyphens, dates ISO 8601 and first. It fits in a README, it fits in a pull request template, and it turns a new name into a lookup instead of an argument.
Then apply it to new names rather than renaming everything in a panic. A wholesale rename of a working project touches every import, link and query at once, and the risk of that is much larger than the inconsistency you set out to fix. Convert the names you touch, keep the written rule where people can see it, and let the older names age out as they get edited. A project that is consistent about everything new is easy to work in within a week.
The part that matters most is the one the title is about. Consistency inside one project beats the theoretically best convention, and it beats it by a wide margin. A project where every name follows a merely adequate rule is easier to work in than one where every name is individually defensible and none of them agrees with the next. That is the difference between a convention and a preference: a preference is yours alone, while a convention is the thing the next person can predict, and prediction is the only property that was ever worth having.
Somebody else's convention
Which brings the question to the case the title does not cover. If you are working inside somebody else's project, or on a codebase that already has a convention, the answer is theirs. Follow it, even where you would have chosen differently, because a mixture produced by two people each applying their own preference is the worst outcome available — worse than either convention applied on its own, and much worse than the one you would have picked. When you do have the authority to change it, change it once, in writing, and convert in a single pass rather than arguing it file by file for a year.