Advertisement

The old joke says there are two hard problems in computer science: cache invalidation, naming things, and off-by-one errors. The naming entry earns its place. Names are the user interface of code — the layer through which every future reader, including you in six months, perceives what the program does. A well-named codebase can be skimmed; a badly-named one must be decrypted.

Naming is hard because it is compression: squeezing a concept, its type, its units, its lifecycle and its intent into a few words. But compression has rules, and they can be learned.

Name the meaning, not the mechanics

The weakest names describe what a thing is made of; the strongest describe what it means. data, info, temp, obj, handler2 are mechanical names — true of almost anything, informative about almost nothing. unpaidInvoices, retryDelayMs, isEmailVerified each carry a claim about the world that the reader can check against the code. When a name states a meaning, a mismatch between name and behaviour becomes visible — which is exactly how good names catch bugs during review.

Two mechanical habits deliver most of this: put units in names that have them (timeoutMs, priceUsd, distanceKm — entire spacecraft have been lost to implied units), and make booleans read as assertions (isActive, hasStock, canRetry) so conditionals read as sentences: if the order can retry, retry it.

Advertisement

Scope decides how long a name should be

The classic fights — i versus index, short versus descriptive — dissolve under one principle: a name's length should grow with its scope. A loop counter alive for three lines can be i; everyone knows it, and a longer name would add noise, not clarity. A module-level constant read from fifty places deserves a full sentence-grade name like MAX_CONCURRENT_UPLOADS_PER_USER. The crime is inversion: cryptic abbreviations with global reach, or ceremonious names for two-line locals.

The same principle governs functions. A private helper called three lines below its definition can be terse; a public API name is a promise made to strangers and should say exactly what it does — including its surprises. If a function is honestly named fetchUserAndUpdateLastSeen, the name itself is telling you it does two things, and that the design, not the name, needs work. Names that resist naming are design feedback.

Consistency beats brilliance

A merely decent naming scheme applied everywhere outperforms scattered brilliance. If deleted things are removed in one module, destroyed in another and archived in a third, every reader pays a lookup tax forever. Pick one verb per concept — get for cheap reads, fetch for remote calls, build for construction, whatever your team likes — and enforce it in review as seriously as tests. The vocabulary of a codebase is an API, and synonyms are bugs in it.

And when you find a lie — a name that no longer matches behaviour after refactors — fix it immediately, whatever the diff noise. A wrong name is worse than a vague one: readers trust it and inherit a false belief. Renaming is the cheapest documentation update in existence, and modern editors make it a keystroke. The codebase reads the way it is named; name it like you'll be the one reading.

Advertisement

Booleans: is, has, should, and why a bare adjective is ambiguous

A boolean variable or function named `visible` or `active` leaves genuine ambiguity about whether it represents current state or a request to change it, while prefixing it with `is`, `has`, or `should` — `isVisible`, `hasPermission`, `shouldRetry` — removes that ambiguity immediately and consistently, turning a name into something that reads as a complete, unambiguous yes-or-no question on its own. This convention costs nothing beyond typing a few extra characters and pays for itself every single time someone reads the name afterward without needing to check the surrounding code to confirm what it actually represents.

Plurals for collections, singular for single items — enforced strictly, not loosely

Naming a variable that holds an array `user` rather than `users` reliably misleads a reader into treating it as a single item, at least until the first time it is iterated over and the mismatch becomes obvious the hard way — consistently using a plural for anything holding a collection, and a strict singular for anything holding one item, is a small, mechanical convention that removes an entire category of momentary confusion at every single point in the code where that variable is read, without requiring the reader to check its declared type or its usage first.

Advertisement

Functions as verbs, values as nouns, and why mixing them signals something

A function named `user` rather than `getUser` or `fetchUser` reads ambiguously as though it might be a plain value rather than something that must be called to produce one, while a variable named `calculate` reads as though it should be callable when it is not — consistently naming functions with a verb, describing the action they perform, and naming values with a noun, describing the thing they represent, keeps this distinction visible directly in the name itself, which matters especially in a dynamically typed language where nothing else in the syntax necessarily makes the distinction obvious at the call site.

Avoiding abbreviations that save the writer time and cost every reader more

An abbreviation like `usrCfg` or `calcTtl` saves its author a handful of keystrokes once, at the moment of writing it, while costing every subsequent reader a small moment of decoding every single time they encounter it afterward — an asymmetry that overwhelmingly favors writing the name out in full, since the total reading cost across a name's entire lifetime, read by potentially dozens of people over months or years, vastly outweighs the trivial, one-time cost of typing a few extra characters when the name was first chosen.

Avoiding names that only make sense in light of history nobody else has

A variable named after an old system it once replaced, or a function named after a long-departed colleague's now-forgotten nickname for a concept, reads as opaque to literally everyone except whoever was present for the original context — a name should be understandable purely from the current codebase and domain, with zero dependency on institutional history a new team member could not possibly have, and any name that fails this test is worth revisiting specifically because its meaning depends entirely on context that will not travel with the code.

Consistent terminology across the whole codebase, not just within one file

Calling the same concept `user` in one module, `customer` in another, and `account` in a third forces every reader to manually build and maintain a mental mapping between three names for one underlying thing, a genuinely avoidable tax that a single, consistently used term across the entire codebase removes entirely — maintaining this consistency deliberately, rather than letting each part of a codebase independently settle on its own preferred vocabulary, is a specific discipline worth enforcing at the team level rather than leaving to individual habit.

Why a name should describe what something is, not how it is currently implemented

Naming a variable `arrayOfUsers` or a function `loopAndFilter` ties the name to an implementation detail that is liable to change independently of what the thing conceptually represents — a name describing the underlying concept (`activeUsers`, `filterEligible`) rather than the current data structure or algorithm stays accurate even after a future refactor changes the implementation, while an implementation-tied name becomes quietly misleading the moment the implementation changes but nobody thinks to rename it to match.

Why a name's length should scale with its scope, not be uniformly short or long

A loop counter used only within three lines of a tight local scope can reasonably be named `i`, since its entire meaning is visible in the few lines it appears in, while a variable or function exported and used across many files needs a name descriptive enough to stand on its own without that same nearby context — applying one uniform standard, either always terse or always verbose regardless of scope, misses this genuine, useful distinction between a name that only has to make sense for three lines and one that has to make sense everywhere it is used.

Why negative booleans invert the reader's effort for no real benefit

A boolean named `isNotDisabled` or `hasNoErrors` forces a reader to mentally apply a double negative every time it appears in a condition, especially once combined with an actual `!` negation operator in code — `!isNotDisabled` is genuinely harder to parse at a glance than a positively-phrased equivalent like `isEnabled` would be, and avoiding negative boolean names entirely, phrasing every boolean in its positive form, removes this small but real, entirely avoidable tax from every single place the value is read.

Why a name should be searchable, not just readable

A single-letter or very generic name is not only harder to understand on sight, it is also nearly impossible to search for reliably across a large codebase, since a search for `x` or `data` returns an overwhelming, unusable number of unrelated matches — choosing names distinctive enough to search for directly, especially for anything referenced across multiple files, is a specific, practical criterion worth checking independently from mere readability, since a name can be perfectly clear in isolation while still being effectively unsearchable at scale.

Why a rule of thumb beats an exhaustive style guide for naming specifically

Naming resists the kind of exhaustive, rule-by-rule style guide that formatting concerns like indentation or bracket placement can be fully automated against, since naming ultimately requires judgment about meaning that a mechanical linter cannot evaluate — a small set of memorable heuristics, like the ones covered throughout this article, generalizes better in practice than an attempt to enumerate every possible naming situation in advance, because judgment transfers to new situations in a way an exhaustive rule list never quite manages to.

Why a name chosen for one language does not always translate cleanly to another

A convention that reads naturally in one language's idiom — a getter prefixed with `get` in Java, a bare property name in a language favoring computed properties — can read as unnecessarily verbose or oddly bare when carried unreflectively into a different language's own conventions, which is a reminder that good naming is relative to the idioms of the language and ecosystem being written in, not a single universal standard applied identically everywhere.

Why a good name should survive being read out of context

A name that only makes sense while looking at the surrounding code it appears in, but reads as meaningless in isolation — in a log line, an error message, a search result — has not fully done its job, and testing a candidate name by imagining it appearing alone, stripped of its usual context, is a quick, useful check that many otherwise reasonable-looking names fail once actually tried.