· 3 min read

Functions need spaces

Snake case makes function names readable verb phrases by writing word boundaries explicitly instead of asking capitalization to impersonate punctuation.

Close-up of metal movable type letters sorted into the compartments of a wooden type case.
Willi Heidelbach, CC BY 2.5

A generated TypeScript patch handed me normalizeHTTPURLSearchParams. I read it twice, then placed the cursor between the capitals to recover the words the name had swallowed. The function itself was fine. Its name behaved like a password.

Function names are verb phrases. Snake case gives those phrases spaces the parser can tolerate. Camel case makes capitalization do two jobs at once: spell the words and separate them. Why are we asking your eyes to infer whitespace when _ is sitting on the keyboard?

names.tsts
normalizeHTTPURLSearchParams()normalize_http_url_search_params()loadUserIDFromURL()load_user_id_from_url()shouldRetryOAuthCallback()should_retry_oauth_callback()

The snake versions take more columns. Good. The words are actually there. You can scan the verb, the object, and the qualifiers without first deciding whether HTTPURL contains two acronyms, one product name, or a cry for help.

Capital letters are overloaded#

Camel case looks disciplined until an acronym arrives. Then every project invents a miniature constitution: HTTP or Http, URL or Url, and whetherOAuth counts as a word, a brand, or an exception somebody remembers from review.

Name the function that parses an HTTP URL.

  • parseHTTPURL
  • parseHttpURL
  • parseHttpUrl
  • parseHTTPUrl
  • parse_http_url
Four camel spellings need an acronym policy; the underscored spelling exposes the three words.

Rust avoids this argument in its API naming guidelines: functions and methods use snake_case, and acronyms become ordinary lowercase words. Python's PEP 8 makes the same choice for functions. These conventions do more than make code look consistent. They remove a naming decision that never deserved review time.

The evidence has an asterisk#

The empirical story is annoyingly mixed. A 2009 identifier study found underscored names were recognized faster while camel-cased names were recognized more accurately. A later eye-tracking study found faster recognition for underscored identifiers, but its participants were mostly trained in that style. Familiarity moves the result.

I haven't found evidence strong enough to declare snake case a universal law of cognition. The smaller claim survives: an underscore is an explicit boundary, while a capital is a clue. Explicit boundaries age better when names collect acronyms, numbers, and domain jargon.

Case already has a job#

Type names benefit from a different silhouette. Rust uses HttpResponse for the type and parse_http_response for the function. You can distinguish the value-level action from the type-level thing before reading either word. In camel-heavy code,HttpResponse and parseHttpResponse differ mainly at the left edge.

Snake case also makes an overgrown name look overgrown. Camel case can compress five words into a smooth dark bar and let you pretend the function has one responsibility. Underscores make every conjunction visible.

preferences.tsts
fetch_and_normalize_user_account_preferences(user_id)// The name is admitting that this is two jobs.const preferences = fetch_user_account_preferences(user_id);return normalize_preferences(preferences);

I made this exact split in a small importer after noticing that the underscored name looked ridiculous in autocomplete. The rename did not fix the function. The embarrassment made me open it, and the body was doing network access and normalization in the same retry loop.

Consistency can still win#

Superior typography does not justify vandalizing an established API. Java's method convention is lower camel case, and JavaScript users expect the same shape. A public TypeScript library full of snake-cased methods creates interop friction that may cost more than the reading benefit.

So I still write parseHttpResponse when an existing TypeScript surface expects it. In a tiny parser I started this week, the first exported function isread_next_token. The parser still mishandles one escaped newline, and I have not had to reread that function name once.