Text

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_caseclient_invoicesPython variables and modules, SQL columns, many config keysThe underscores vanish inside an underlined link, so a URL built from one is often read back with a character missing.
kebab-caseclient-invoicesCSS class names, HTML attributes, URL slugsNot 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.
camelCaseclientInvoicesJavaScript variables and functions, Java methods and fieldsCase is the only thing separating the words, so a filesystem that ignores case collapses it onto its PascalCase twin.
PascalCaseClientInvoicesClass names, React components, type and interface namesAn import spelled clientInvoices still resolves on a case-insensitive machine and fails on a case-sensitive one.
SCREAMING_SNAKE_CASEMAX_RETRY_COUNTConstants, environment variablesWindows 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.
lowercasereadmePackage names, archive members, names that have to travel furthestWith 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.