Name it Well

Why Naming Is the Most Consequential Design Decision You Make

The Importance of Naming Things

“There are only two hard things in Computer Science: cache invalidation and naming things.”  — Phil Karlton, Netscape Engineer

Few phrases have captured the engineering zeitgeist as perfectly as Phil Karlton’s deceptively simple quip. Karlton, who worked at SGI in the early 1990s, reportedly said this while his volleyball team was heading to the gym. What began as an offhand remark became one of the most quoted lines in software engineering — because it is, quite simply, true.

The reason naming matters so fundamentally is captured in another celebrated maxim, often attributed to Donald Knuth and Harold Abelson:

“Programs are meant to be read by humans and only incidentally for computers to execute.”  — Harold Abelson, Structure and Interpretation of Computer Programs

A computer does not care whether a variable is called `x`, `temp`, or `monthlyInterestRate`. The machine executes bytecode, not prose. But every name in a codebase is a message sent forward in time — to a colleague, a new hire, or to your future self at 11pm debugging a production incident. Naming is therefore not a cosmetic concern. It is a communication contract.

The scope of naming in software is vast. It encompasses variable names, function names, class names, module names, database schema columns, API endpoint paths, microservice identifiers, configuration keys, and even the names of entire products and platforms. Each one carries cognitive weight. Each one either illuminates intent or obscures it.

Peter Hilton, writing on the subject, frames it elegantly: naming is part of micro-design — the small-scale craft activity that makes code clean, readable, and maintainable. As he notes, naming matters for both ideological and practical reasons: “we want names to exhibit truth and beauty: to be the right names, and to make our code clean and beautiful.” But beyond aesthetics, the practical stakes are even higher.

Martin Fowler, the renowned software architect, put it plainly: “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” Names are the primary tool by which human understanding is either built or destroyed.

Why Naming Is Hard

If naming were easy, Phil Karlton’s joke would not have endured for thirty years. The difficulty is not merely a matter of creativity or vocabulary — it is deeply rooted in the nature of abstraction, communication, and the unknowable future.

The Problem of Abstraction

Software is fundamentally about abstraction: taking complex, messy reality and modeling it with clean, discrete concepts. A name is an attempt to compress meaning into a short label. This compression is inherently lossy. The moment you name something `UserManager`, you have made an implicit claim about what that thing is and does — and you will very likely be wrong the moment the requirements change.

David Karlton, writing about his father Phil’s famous quote on the family blog, offered a profound insight: “To do well at both [cache invalidation and naming], you need to be able to predict the future well. A name is good if it survives into its future context, but of course you don’t know what other names it will coexist with.” This is the temporal dimension of naming — names are chosen now but must live in a future we cannot fully anticipate.

The Challenge of Shared Understanding

Naming is not merely a solo cognitive exercise — it is a social one. A name carries different connotations to different people. In Domain-Driven Design (DDD), Eric Evans argued for the creation of a “Ubiquitous Language” — a shared vocabulary between developers and domain experts used consistently across documentation, design, code, and interfaces. Without such alignment, the same concept gets called `Customer` in the UI, `Client` in the database, and `Account` in the API — causing endless confusion.

Darren Broemmer, writing for Medium, captures the DDD insight: “There is an entire book and discipline centered around the idea that building software is much easier if we simply use the same names for things throughout the documentation, design, code, and interfaces.” Achieving that consistency across an entire organization is genuinely, deeply hard.

The Context Problem

Names exist in context. A variable called `balance` is clear in a banking application, ambiguous in a logistics system (balance of goods? weight balance?), and opaque in a physics engine. A function called `process()` tells you almost nothing. The challenge is always to name something relative to its context without making the name so verbose that it becomes unwieldy.

The abbreviation trap is real: `bal`, `tmp`, `calc1`, or `usr` all pass the compiler but fail the reader. A new engineer joining the team has no way of knowing whether `bal` means balance, ballot, billing, or something else entirely. The guessing game is a tax on every future developer who touches that code.

Naming Is a Design Activity

Perhaps the deepest reason naming is hard is that a good name requires a clear understanding of what the thing actually is. If you struggle to name a function, it is often because that function is doing too many things. If you cannot name a class precisely, it is often because the class has not yet been properly designed. Naming difficulty is frequently a symptom of unclear design, making the naming process itself a diagnostic tool — one that surfaces conceptual fog that would otherwise remain hidden until much later.

The Cost of Naming Incorrectly

Bad names are not merely aesthetic failures. They have measurable, compounding costs across the entire lifecycle of a software system.

The Cognitive Tax on Developers

When variable names like “temp” and “x1″ are scattered everywhere, functions stretch over 300 lines with zero comments, and the original developer left the company years ago”, developers face an enormous mental translation overhead. Every time a developer encounters an ambiguous or misleading name, they must pause, reason, possibly read surrounding code, and construct a mental model of what it means. This is not free.

According to Stripe’s Developer Coefficient Report, engineers spend up to 42% of their time maintaining “bad code” — effectively managing technical debt — instead of creating new value. While not all of this is attributable to naming alone, poor naming is a primary driver of code that is difficult to understand, modify, and extend.

The Bug Amplifier

Names that do not accurately reflect intent create a breeding ground for bugs. When a variable called `userCount` silently becomes a count of active users only — but its name still says `userCount` — every developer who reads that name makes a false assumption. IBM research estimates that fixing a bug in production can cost up to 30 times more than fixing it during development. Misleading names make bugs more likely to reach production in the first place, dramatically multiplying this cost.

The technical debt literature makes this explicit. IBM’s analysis of technical debt notes that “code debt results from rushed development, inconsistent coding practices and poor documentation. When programmers take shortcuts — such as using unclear variable names or failing to follow industry standards — the amount of technical debt accumulates, making debugging and maintenance time-consuming.”

The Compounding Effect of Technical Debt

Ward Cunningham, who coined the term “technical debt” in 1992, described it as a financial metaphor: “A little debt speeds development so long as it is paid back promptly with a rewrite… The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt.” Poor naming is one of the most pervasive forms of this debt — invisible, seemingly harmless in any individual instance, but ruinous in aggregate.

Research published in ScienceDirect’s software engineering journals found that across organizations of all sizes, on average 25% of development effort is wasted on technical debt-caused issues. Furthermore, organizations with high technical debt spend 40% more on maintenance costs than those that invest in code quality from the outset.

The scale of the problem is staggering. In 2025, technical debt in the United States alone was estimated to cost $2.41 trillion annually, with more than 50% of companies allocating over a quarter of their entire IT budget just to managing this debt.

The Onboarding and Retention Cost

Poor naming creates invisible organizational costs in hiring and retention. When a codebase is riddled with cryptic, misleading, or inconsistent names, onboarding new developers takes longer — sometimes dramatically so. Engineers who find themselves constantly confused and frustrated by poorly named code develop lower morale and are more likely to leave. One technical auditor during a startup’s Series B fundraise flagged poor naming as “evidence of rushed engineering” — signaling to investors that the foundations of the company were fragile.

Flag Debt: A Specific Form of Naming Cost

A particularly insidious form of naming debt arises in modern development with feature flags. As teams adopt feature flags at scale, poorly named or undocumented flags accumulate into what practitioners call “flag debt” — code littered with ambiguous toggles and legacy flags with names like `flag1` or `featureX` that no one dares remove. LaunchDarkly’s documentation explicitly notes that managing flag debt “starts with a proper naming strategy so that the purpose of a flag is clear.”

Refactoring to Correct Naming Issues

Once bad names have proliferated through a system, correcting them is far from trivial. Renaming is one of the most common — and most dangerous — forms of refactoring, particularly in the context of public APIs and widely adopted libraries.

Renaming as Refactoring

Research by Danny Dig and Ralph Johnson at the University of Illinois, studying API evolution in frameworks like Eclipse and Struts, found a startling result: over 80% of API changes that break existing applications are refactorings — and renaming is among the most frequent. Their research showed that even though refactorings represent a small percentage of all API changes, they have a disproportionately large impact on backwards compatibility.

For internal code, modern IDEs provide automated rename refactoring that can cascade changes across a codebase. But this is only safe within a controlled environment. The moment a name becomes part of a public interface — an API endpoint, a library method, a database column name, a configuration key — renaming becomes an operation with external blast radius.

The Migration and Compatibility Challenge

When a library or platform renames a public symbol, every consumer of that API must be updated. The research literature documents this clearly: in practice, when a method is renamed, “for compatibility reasons, component producers support both the old and new method for a while. After all the references to old method were replaced, the method is deleted since it’s a remnant.” This dual-support period is costly to maintain and confusing to document.

A vivid real-world example is React’s transition from class components to function components with hooks — a paradigm shift that took years for large codebases to fully adopt. As engineers writing on the subject at Martin Fowler’s site note: “By the time teams managed to migrate, more breaking changes were often already on the horizon. For library developers, this situation creates a burden. Maintaining multiple older versions to support users who haven’t migrated is both costly and time-consuming.”

Research into Java breaking changes found that 47% of breaking changes in real-world Java libraries were attributable to refactorings, with method renamings among the most impactful. Furthermore, a study of 81.5% of projects found they keep outdated dependencies — meaning that even when names are corrected upstream, downstream consumers often fail to adopt the corrections for years.

Tooling Approaches to Migration

The software engineering community has developed several approaches to automate or ease naming migrations. Codemods — tools that operate on Abstract Syntax Trees (ASTs) to programmatically transform code — allow library maintainers to ship automated migration scripts alongside breaking changes. Tools like jscodeshift, Hypermod, and OpenRewrite enable large-scale automated renames across codebases that the developer does not fully control.

For REST APIs, the deprecation lifecycle is the standard pattern: introduce the new name (often a new endpoint path or response field name), mark the old name as deprecated with a clear sunset date, run both in parallel, then remove the old name after sufficient migration time. The OpenAPI 3.1 specification even provides a `deprecated` keyword to make this machine-readable, enabling SDKs to automatically warn developers when they use a deprecated name.

According to API management research: “Unmanaged API changes cause 40% of integration failures and cost development teams an average of 15–20 hours per incident in emergency fixes. Organizations that implement proactive change management strategies report 70% fewer update-related incidents and reduce migration time from weeks to days.”

The Dual-Name Compatibility Period

The pragmatic solution to naming corrections at scale is the dual-name compatibility period: the old name continues to work (possibly issuing deprecation warnings) while the new name becomes the canonical choice. This is the strategy employed by most mature libraries and platforms. It decouples the release of the corrected name from the forced migration of all consumers — a critical distinction in ecosystems with thousands of downstream users.

However, this approach has costs: it increases maintenance burden, creates documentation confusion, and requires careful versioning. As REST API design guidance notes, removing or renaming fields, endpoints, or parameters without a compatibility period is a breaking change that “can disrupt existing client applications, resulting in errors and loss of functionality.”

Strategy to Avoid Incorrect Names Through Beta, Experimental, and Feature Releases

The most effective cure for naming problems is prevention. The software industry has developed a suite of strategies — from beta prefixes to feature flags to semantic versioning — that create safe spaces for names to be evaluated and refined before they harden into permanent contracts.

Beta and Experimental Namespaces

One of the most powerful conventions is the deliberate labeling of new names as provisional. Prefixing APIs, endpoints, or features with `beta`, `experimental`, or `preview` signals to consumers that the name (and the interface it represents) has not yet stabilized. This label is a contract: it says “we are still learning what this thing should be called.”

This approach is widely used across the industry. Google’s Cloud services, AWS, Azure, and most major platform providers use preview or beta labels for new APIs precisely to avoid prematurely locking in names. As TinyMCE’s engineering team notes, the beta label “is important to set users’ expectations, especially if your reputation is built on solid releases.” Crucially, during beta, “we can’t guarantee there won’t be changes to APIs” — which means consumers opt in to instability knowingly.

Feature Flags as Naming Experiments

Feature flags — also known as feature toggles, release toggles, or feature flippers — are a mechanism to deploy new code (including new names) without exposing it to all users immediately. They enable teams to decouple deployment from release: a new API name can be deployed to production infrastructure while only being surfaced to a subset of users or internal testers.

LaunchDarkly’s documentation captures the central insight: feature flags allow developers “to test features directly in the production environment without impacting the entire codebase” and provide “a kill switch for when things go sideways.” Applied to naming, this means a new name can be tested in production with real users — and if the name proves confusing or incorrect, it can be changed before it hardens into a public commitment.

Feature flags also enable structured naming experiments. Enterprise naming conventions for flags even encode the experimental nature in the name itself. As one implementation guide recommends: feature.{domain}.{name}.beta or experiment.{domain}.{name} — patterns that build the provisional status directly into the flag’s identifier.

Canary Releases and Gradual Rollouts

Canary releases extend the feature flag concept to progressive audience exposure. Rather than releasing a new name (or interface) to all users at once, it is first released to 1-5% of users — often internal employees or opted-in beta testers — and then progressively widened as confidence grows. This allows real-world feedback on whether a name is understood, discovered, and used as intended before it becomes universal.

Research into gradual rollout practices shows that teams using this approach can test naming decisions against actual user behavior: “We enabled the engine for a limited, targeted user group using feature signals while closely monitoring performance and user interaction. This enabled us to detect flaws and enhance the functionality prior to its wider implementation.” The same principle applies directly to naming — user confusion, support tickets, and usage patterns can reveal when a name is not resonating.

Semantic Versioning as a Naming Safety Net

Semantic versioning (SemVer) — the convention of versioning software as MAJOR.MINOR.PATCH — provides a structural framework for communicating when names have changed in a breaking way. A MAJOR version bump explicitly signals that names (and interfaces) may have changed incompatibly, giving consumers a clear signal to review migration guides. API versioning strategies (embedding `v1`, `v2` in endpoint paths, or using Accept headers for media-type versioning) serve the same purpose: they create parallel naming spaces that allow the old name and the new name to coexist, giving consumers time to migrate.

The Naming Convention as a First-Class Design Artifact

The deepest preventive strategy is to treat naming conventions as a first-class design artifact, not an afterthought. Teams that establish and enforce naming standards early — through documented style guides, code review checklists, linting rules, and automated tooling — reduce the incidence of names that need correction later.

Consistent naming conventions provide multiple benefits: they reduce the cognitive overhead for developers reading unfamiliar code, they make automated tooling (search, refactoring, documentation generation) more effective, and they create a shared vocabulary that aligns teams. Harness, a leading feature management platform, recommends that flag names should “indicate the feature name, environment, or user segment” — for instance `checkout_redesign_v1` or `beta_feature_onboarding` rather than `flag1` or `featureX`.

The investment pays compound returns. Every hour spent establishing clear naming standards is returned many times over in reduced debugging time, faster onboarding, and fewer production incidents caused by misunderstood identifiers.

A Case Study in Naming as Asset: Oracle v. Google and the Java API

The Case at a Glance

Few episodes in computing history illustrate the long-term value of well-chosen API names more vividly than the decade-long legal battle between Oracle and Google. On the surface, it was a copyright dispute. At its technical core, it was a dispute almost entirely about names — the names of classes, methods, and packages that Sun Microsystems had spent years carefully crafting for the Java platform.

Google, building Android in 2007, copied the declaring code of 37 of the Java API’s 166 packages. The declaring code includes the name for each function — for example, Math.max() — its syntax, and its taxonomic position within Java’s class hierarchy. Google independently wrote entirely new implementing code underneath. The machine did not care what the names were. The humans did.

Why Google Needed the Names, Not Just the Logic

Google’s strategic calculus was straightforward. By the time Android was in development, millions of software developers worldwide had internalised the Java API’s vocabulary. Collections.sort(), java.util.ArrayList, String.format() — these were not just convenient shorthand. They were the accumulated cognitive investment of an entire generation of programmers. By preserving the naming surface exactly, Google ensured that any Java developer could write Android apps from day one, with no new vocabulary to learn.

“Java is a known language — developers know it and don’t have to learn it. Google’s use of Java meant not having to create that ecosystem from scratch.”  — Developer community analysis of Android’s launch strategy

Google’s then-executive chairman Eric Schmidt had attempted to license Java from Sun. Negotiations broke down over Google’s desire to fork the platform. Sun’s president Jonathan Schwartz publicly congratulated Google on Android’s launch day — but Oracle, after acquiring Sun in 2010, had no such sentiment. Larry Ellison sued immediately.  [Wikipedia: Oracle v. Google]

The Legal Question Was a Naming Question

The Federal Circuit held that the structure, sequence, and organisation of API declarations — the names and how they were organised — were copyrightable expression. The Supreme Court ultimately ruled in Google’s favour on fair use grounds, finding that the 11,500 lines of copied declaring code represented only about 0.4% of the total Java codebase, and that Google’s use was transformative in bringing Java-familiar programming to a new context.  [Google LLC v. Oracle America, Inc., 593 U.S. 1 (2021)]

Oracle’s counter-argument captured precisely why names accrue commercial value: “Google’s theory is that, having invested all those resources to create a program popular with platform developers and app programmers alike, Oracle should be required to let a competitor copy its code so that it can coopt the fan base.”  [Oracle legal brief, via SCOTUSblog]

Java’s API Design — A Praised Vocabulary

The Java API did not become contested because it was ordinary. It became contested because it was extraordinary. The names Sun’s engineers chose — across collections, I/O, concurrency, and mathematics — were praised by practitioners for their clarity, consistency, and intuitive design.

Joshua Bloch, who led the design and implementation of the Java Collections Framework and the java.math package at Sun, became the canonical voice on what good Java API design looks like. His book Effective Java, first published in 2001, won the Jolt Award from Software Development Magazine — the industry’s most prestigious peer recognition — and remains a definitive reference more than two decades later.  [oracle.com, More Effective Java With Joshua Bloch]

“If you need to read an API’s source code, there is probably a problem in its design.”  — Jaroslav Tulach, Practical API Design: Confessions of a Java Framework Architect

Bloch articulated the design aspiration directly: “You don’t really know whether you have the API right until you use it. Nobody gets it right the first time, even if they have years of experience.” Yet for the Collections Framework, the design came remarkably close. The names List, Set, Map, Queue, Iterator, and their implementations — ArrayList, HashMap, LinkedList — are models of the principle that a name should reveal intent without exposing implementation. They have remained stable and largely uncontroversial across 30 years of Java releases.  [artima.com, Josh Bloch on Design]

Andrew Binstock, writing in Java Magazine, described Effective Java as “supremely readable” and recommended it “without reservation to all Java programmers who are past the beginner stage.” The DZone Java API Best Practices refcard captures the philosophical underpinning: “API design is as much, if not moreso, an art form that requires creativity and a gut feeling to be developed over many years, rather than an exact science.”  [dzone.com/refcardz/java-api-best-practices]

The NetBeans API architect Jaroslav Tulach, in Practical API Design, offered perhaps the most elegant summary of why API names demand permanence in mind:

“APIs are like stars: once someone discovers them, they need to stay and behave well forever.”  — Jaroslav Tulach, Practical API Design: Confessions of a Java Framework Architect

Names Accumulate Value Over Time

There is a principle that Oracle v. Google crystallises more vividly than any abstract argument: names accumulate value over time. A name chosen on day one carries no particular weight. But a name that is used consistently, learned by millions, embedded in tutorials, Stack Overflow answers, textbooks, and production codebases across two decades — that name becomes infrastructure. It stops being a label and becomes a shared reference point for an entire profession.

This is why the Java API names were worth litigating over at the Supreme Court level. Not because the words themselves were brilliant, but because they had been internalised at scale. Math.max() is not a poetic name. It is a transparent, predictable, utterly unsurprising name — and that is precisely its virtue. Sun’s engineers chose names that were easy to learn, easy to remember, and easy to guess. Twenty-five years of repetition turned those modest virtues into something worth billions of dollars.

The lesson for everyday software engineering is clear: the names you choose today will be worth more — or cost more — with every passing year. A good name earns compound interest. A poor one accumulates compound debt. Unlike financial debt, naming debt cannot be refinanced; it can only be paid down through migration, and migrations are always expensive. Treat every name as an investment whose return horizon is measured in years, not sprints.

“Your code is for a human first and a computer second. Humans need good names.”  — namingthings.co In an industry where technical debt costs trillions of dollars annually and developers spend nearly half their time fighting existing code rather than building new things, the humble art of naming well is one of the highest-leverage investments any engineering team can make.

Conclusion

Phil Karlton’s joke has endured not because naming is impossibly hard, but because it is harder than it looks — and because the consequences of getting it wrong are larger and more persistent than most engineers initially appreciate. Names are not decoration. They are the primary medium through which code communicates intention, and through which teams coordinate understanding across time and space.

The path forward is clear even if it is not easy: invest in naming upfront, treat naming difficulty as a design signal, use beta and experimental labels to defer naming commitments until confidence is earned, and build migration strategies that protect consumers when corrections become necessary.

5 1 vote
Article Rating
Subscribe
Notify of
0 Comments
0
Would love your thoughts, please comment.x
()
x