naming-things

Use when choosing an identifier for a variable, function, type, module, flag, config key, table, or event; when a name is misleading, abbreviated, inconsistent with the rest of the codebase, or inherited from something it no longer describes; when weighing whether a rename is worth its cost; when the name being renamed is also a token something resolves — a path segment, a URL, a manifest field, an invocation prefix — so a search-and-replace would break it silently; or when code needs a comment to explain what a thing holds or what a function actually does. Covers partitioning occurrences by whether a human reads them or something resolves them, and pinning the ones deliberately left alone.

Overview

Names are the cheapest documentation to write and the most expensive to change once they reach a wire format, a database column, or another team's code. A name is read hundreds of times at sites that have none of the context of its definition, and it is the only documentation that cannot go stale silently — it goes wrong loudly, in every reader's head.

When to use

  • Introducing any identifier that will outlive the function it appears in.
  • A reviewer, a colleague, or you had to explain what something holds.
  • The same concept has different names in the API, the database, and the UI.
  • Deciding whether a bad name is worth a rename now.
  • Not for: mechanics of executing a large rename safely (refactoring-safely), or designing the shape of an interface rather than its vocabulary (designing-apis).

Length is proportional to scope

Scope of useLengthShape
Loop index, 1–3 line lifetime1–2 charactersi, k, v
Local in a function under 20 lines4–12 charactersrow, pending, cutoff
Parameter or field read by callers8–20 charactersretryBudget, sourceEncoding
Exported or module-level12–30 characters, fully spelledmaxConcurrentUploads
Wire format, column, config key, metricAs long as needed, namespaced, never abbreviatedbilling.invoice.line_item_id

The principle underneath: a name must be readable at its furthest point of use, not at its definition. A field called n is fine inside a six-line function and a defect on a public type — not as a matter of taste, but because the distant reader has none of the context the definition site has.

The inverse holds and is less often said: long names in tiny scopes are noise. currentIterationIndexValue makes a three-line loop harder to read, not easier. Verbosity is not clarity.

What a name should encode

In priority order: what it is in the domain > what role it plays here > its type > how it is implemented.

  • Type-encoding names (strName, userList, arr, IFoo, objData) go stale the instant the type changes, and the type is already stated by the language or shown by the tooling. The one case that earns it: genuinely unshaped data where the encoding is the only fact worth carrying, such as raw_json_body at a wire edge.
  • Implementation-encoding names (redisSession, linkedListQueue, s3Path) become lies at exactly the moment you change the implementation — the moment you are least able to also update every reference. Name the role: sessionStore, pendingWork, artifactLocation.
  • Names built from Manager, Handler, Processor, Util, Helper, Data, Info, Service, or Wrapper usually name nothing; they name a decision that was postponed. The test: can you say what it does not do. If not, the name is empty and the design behind it probably is too.

Booleans, functions, collections

Booleans name the true state, affirmatively. isEnabled, hasPendingWrites, shouldRetry read as English at the call site. Never name the negative — notReady, disableCache, isNotValid produce a double negative at every use, and inverted-logic bugs cluster there. Two further rules:

  • A boolean parameter is unreadable at the call site no matter how well named: a call reading render(doc, true, false) conveys nothing. The fix is an enum, named arguments, or two functions — not a better name.
  • Three booleans that describe one thing (isLoading, isError, isEmpty) permit eight combinations of which half are nonsense. Name the state, not the flags.

Functions are named for their effect if they have one, and for their return if they do not. A command that mutates takes an imperative verb: flushBuffer, revokeToken. A query takes a noun or a get/find/is form: activeSessions, findUserByEmail. A query whose name promises no effect but mutates is the most damaging misnomer class there is, because callers legitimately assume they can call it twice, cache it, reorder it, or delete the call.

Prefixes carry a contract that callers rely on without reading the body. Keep them honest:

PrefixPromisesBroken by
get, is, hasCheap, in-memory, no failureHiding I/O behind it, so callers put it in a loop
fetch, load, readI/O, latency, can failReturning stale cache with no indication
compute, calculateExpensive but pure and repeatableCaching into shared state
tryReturns failure rather than raisingRaising anyway
create, ensureCreates / creates-if-absentConflating the two, so retries fail

A function whose honest name needs "And" does two things. Split it, or keep the honest name — but do not shorten the name to hide it. The name is diagnosing the design.

Collections are plural and name the element: pendingInvoices, not invoiceList or data. A map is named for the relation it encodes: sessionsByUserId beats sessionMap, and it removes the question of which side is the key.

Abbreviations, jargon, and inherited misnomers

Spell it out unless the abbreviation is more recognizable than the expansion within the domain (id, url, http, db, io, max, min). The test is not "do I understand it" but "would a new reader and a repo-wide search both find it" — usrMgr fails both.

Domain jargon the business itself uses is not an abbreviation; it is the correct name. Translating a domain term into clearer English installs a permanent translation cost between the code and every conversation about the code.

When the codebase already calls a thing by a wrong name consistently, consistency wins. A codebase with one correct name and forty wrong ones has forty-one names for one thing and zero searchability. Exactly two options: use the existing name, or rename all forty-one in one commit. Introducing the forty-second variant "correctly" is the worst of the three and is the most common choice.

The corollary is one word per concept across code, tests, logs, docs, tickets, and speech. When the same entity is a customer in the API, a user in the database, and an account in the UI, every conversation pays a translation tax and every bug report is ambiguous. Collapsing that to one word is worth a dedicated commit.

Renaming has a real price

The name lives inCostWhen to do it
A local or private functionSeconds, tool-assistedImmediately, whenever it is wrong
A module's internal APIMinutes, plus conflicts in every open branchWith the next change to that module
An exported or public APIEvery consumer, plus a deprecation windowOnly with an alias and a version bump
A wire field, column, config key, metric, or log fieldA migration and a compatibility period; dashboards and alerts break silently and nobody notices for weeksRarely, never as a drive-by

Rename in its own commit, containing nothing else, so review and revert both stay possible.

The reliable signal that a name is wrong: you keep having to explain it. Every comment clarifying what a variable holds, every "actually, this is…" in review, every time the same question is asked twice — the name is doing negative work and the comment is a patch on it. Delete the comment, fix the name.

The second signal: you cannot think of a name at all. Five minutes of not naming something usually means the thing is two things. That is a design problem presenting as a vocabulary problem, and no amount of thesaurus work resolves it.

One string doing two jobs

The table above sorts occurrences by where the name lives, which works while every occurrence is the same kind of thing. Some names are two kinds at once: a display word a human reads, and a token something resolves — a path segment, an invocation prefix, a manifest name field, a config key, a URL. Renaming a product while its old spelling stays valid for users is the common case, and the two roles are not separable by file. One page holds both, occasionally one sentence does.

So partition by role before touching anything, and ask it of each occurrence rather than of each file: does a human read this, or does something resolve it? Rename the first kind. Leave the second entirely alone. A search-and-replace is wrong on every occurrence of the second kind, and it is wrong silently — nothing fails at the moment of the edit, because a config key that no longer matches anything is a valid string.

Then pin what you left. The rename has made the next mistake more likely rather than less: both spellings now sit in the same files, and whoever reads them next sees an inconsistency and tidies it. Care does not survive that reading; a test asserting the identifiers are still bare does. This is the unusual rename whose regression test guards against the change after it rather than against itself.

Common mistakes

SymptomReal cause
A comment explaining what a variable holdsThe name should have said it
data, info, obj, temp, result2Naming postponed and never returned to
A condition reading as a double negativeBoolean named for the false state
One concept, three names across layersNo shared vocabulary; each layer named it locally
Renamed one occurrence "correctly"Added the Nth variant; searchability now worse than before
A get accessor that makes a network callPrefix contract broken; callers put it in a loop
Rename buried inside a behavior changeNeither reviewable; the revert takes both
Five minutes stuck on a nameThe thing is two things
A rename landed and a config key, route, or manifest field brokeOccurrences were partitioned by file; the same string was a display name in some of them and an identifier in others

Red flags

  • "I'll name it properly later."
  • "Everyone here knows what that abbreviation means."
  • "It's just a temp variable."
  • "I'll add a comment explaining what it holds."
  • "The existing name is wrong, so I'll use the right one for mine."
  • "Both spellings are in this file, let me make them consistent."

More in Reading code