These aren't interchangeable style choices. Each convention lives in specific places for specific reasons — and using the wrong one causes bugs that are genuinely hard to spot, because the names look almost right.

Every programmer has opinions about userId versus user_id versus user-id, and most treat it as taste. It mostly isn't. Each convention became standard in particular places for concrete reasons, and the languages, file systems, and protocols you work with have expectations baked in. Fight those expectations and you get a category of bug that's especially nasty, because the wrong name looks almost exactly like the right one.

The conventions, and one definition

The main players: camelCase (first word lower, later words capitalised), PascalCase (every word capitalised), snake_case (lowercase, underscores), and kebab-case (lowercase, hyphens). Wikipedia pins down the one with the most literal name:

"Snake case is the naming convention in which each space is replaced with an underscore (_) character, and words are written in all lower case. It is a commonly used naming convention in computing, for example for variable and subroutine names, and for filenames."

— Wikipedia, "Snake case" (CC BY-SA 4.0)

Each of the others has a similar home turf. The skill isn't preferring one — it's knowing which one the context expects.

Where each one belongs

The conventions cluster by ecosystem, and the clustering is real:

These aren't laws, but they're strong conventions, and matching them makes your code look native instead of transplanted from another language.

The kebab-case rule that's actually a rule

One of these is enforced by machines, not just style guides. You cannot use kebab-case for identifiers in most languages, because the hyphen is the minus operator — user-id parses as "user minus id," not a variable name. That's why URLs and CSS (where hyphens are fine) use kebab-case, while variables (where hyphens mean subtraction) never can. This is the one place the choice isn't convention at all: the language will reject a hyphen in a name, so the "style" is really a syntax constraint wearing a style's clothes.

The bug that hides in the difference

Here's why this matters beyond aesthetics. Mixing conventions across a boundary creates silent mismatches. A JavaScript front end sends JSON with camelCase keys; a Python back end expects snake_case — and userId simply doesn't match user_id, so the field arrives as undefined with no error, just a quietly missing value. Databases add another trap: some systems fold unquoted identifiers to a particular case, so userName and username can collide or fail to match depending on quoting. These bugs are brutal precisely because the names look right at a glance — the eye reads userId and user_id as "the same thing," and the machine does not.

Consistency beats correctness

The single most important rule is quieter than any of the above: be consistent within a boundary. A codebase that uses snake_case everywhere is easier to work in than one that's "correct" in each spot but switches conventions every file. Pick the convention the ecosystem expects, apply it uniformly, and convert deliberately at the edges where two worlds meet (that JSON-to-database seam). The goal is that a reader never has to wonder which style a given layer uses.

SCREAMING_SNAKE_CASE and constants

One variant deserves its own mention: uppercase snake_case, sometimes called SCREAMING_SNAKE_CASE or UPPER_SNAKE_CASE. It's the near-universal convention for constants and environment variables — MAX_RETRIES, DATABASE_URL, API_KEY. The visual loudness is the point: it signals "this value is fixed and should not be reassigned." Environment variables on every major operating system follow this convention, configuration files use it, and most linters will flag a constant that isn't uppercased. It's one of the few casing conventions that genuinely earns its status as a rule rather than a guideline, because the alternative — a constant named maxRetries that looks like any other variable — actively hides the intent.

Filenames are their own world

File naming sits at the intersection of several constraints that don't apply to code. Filesystems vary in case sensitivity (Linux is case-sensitive, macOS is case-preserving but insensitive by default, Windows is insensitive), so a project with both Utils.js and utils.js works on Linux and explodes on Mac. URLs treat hyphens and underscores differently — search engines historically treated hyphens as word separators but not underscores, which is why kebab-case dominates web content filenames. Spaces in filenames are legal everywhere but break shell scripts, Makefiles, and anyone who forgets to quote a path. The safest cross-platform filename convention is lowercase kebab-case with no spaces: user-profile.tsx, not UserProfile.tsx or user_profile.tsx. It survives every filesystem, every URL, and every shell.

Automated enforcement beats documentation

No amount of convention documentation survives contact with a team under deadline pressure. The conventions that actually hold are the ones enforced by tooling: a linter that flags user_id in a JavaScript file, a database migration check that rejects mixed-case column names, a CI step that fails on filenames with spaces. ESLint's camelcase rule, Python's snake_case enforcement in pylint, and Rust's compiler warnings for non-idiomatic naming all exist because the language communities learned that style guides alone don't prevent drift. If a convention matters enough to document, it matters enough to lint. If it doesn't matter enough to lint, reconsider whether it matters at all.

Convert cleanly at the seams

When you do have to move between conventions, do it mechanically rather than by hand. A case converter transforms a name between camelCase, snake_case, kebab-case, and PascalCase instantly, which is exactly what you need at an API boundary or when porting code between languages. A slug generator handles the specific job of turning a title into clean kebab-case for URLs and filenames, where the rules about safe characters are strictest. And when a mismatch is causing a silent bug, a text diff makes userId versus user_id visible side by side so you can see the seam the machine tripped on. The convention isn't taste — it's a contract with the tools you're using.

← All articles