There are two hard problems in computer science: cache invalidation, naming things, and off-by-one errors. The joke is evergreen because naming really is that hard. But the difficulty isn’t just in choosing the first name. It’s in all the moments after, when the thing changes and the name doesn’t.

The Drift

A function starts its life as processOrder. It processes an order. The name is accurate. The code is clear. Everything is fine.

Six months later, processOrder also sends a confirmation email, updates inventory, logs analytics events, and — due to a feature request that seemed small at the time — conditionally applies a discount. The function still processes the order, technically. But the name now describes roughly 20% of what the function does.

This is naming debt. It accrues the same way technical debt does: silently, incrementally, with each change that expands the function’s responsibilities without expanding its name.

The Cost

Naming debt is expensive in a specific way: it costs reader time.

When you encounter processOrder for the first time, you make assumptions based on the name. You expect it to process an order. You don’t expect it to send emails. So when a bug in the email-sending logic surfaces, processOrder is not where you look first. You check the email service, the notification system, the queue — everywhere the name suggests the behavior should live. Eventually, maybe, you grep for the email template string and find yourself staring at processOrder, wondering why email logic lives inside order processing.

That investigation time is the interest payment on naming debt. Every developer who reads that function pays it. Every new team member pays it. Every future version of yourself, returning to this code after six months away, pays it.

Names Are Promises

A good name is a contract. It promises the reader that the thing behind the name behaves in a way the name implies. calculateTax calculates tax. validateInput validates input. sendNotification sends a notification.

When the implementation outgrows the name, the contract is broken. The name still makes a promise, but the implementation no longer keeps it. This creates a specific kind of cognitive friction: the reader has to hold two models simultaneously — what the name says and what the code actually does — and reconcile the difference.

This is worse than a bad name. A function called doStuff makes no promises and sets no expectations. You know you need to read the implementation. But a function called processOrder that secretly sends emails is actively misleading. It points your mental model in the wrong direction.

The Renaming Reluctance

If naming debt is so costly, why don’t we fix it more often?

Partly because renaming feels trivial. It doesn’t add features. It doesn’t fix bugs (directly). It doesn’t move a ticket from “in progress” to “done.” In a culture that values visible output, renaming a function feels like rearranging deck chairs.

Partly because renaming is scary. A function called from forty places is a function you rename in forty-one places (including the definition). Miss one, and you’ve introduced a build failure or, worse, a runtime error. The risk-to-reward ratio feels unfavorable: substantial risk of breaking something, with the reward being… a better name.

And partly because of attachment. The original name was chosen with care, at a moment when it accurately described the thing. Admitting it no longer fits means admitting the code has drifted from its original design. That can feel like admitting a failure rather than recognizing natural evolution.

When to Pay the Debt

Not every imperfect name needs immediate correction. Like all technical debt, naming debt requires judgment about when the cost of carrying it exceeds the cost of fixing it.

Rename when the name actively misleads. If a function’s name causes people to look in the wrong place during debugging, the name is causing measurable harm. Fix it.

Rename when you’re already in the file. The marginal cost of renaming during an existing change is much lower than making a dedicated renaming PR. If you’re modifying processOrder and you notice it does six things, consider renaming it while you’re there.

Rename when you’re about to add to the confusion. If you’re adding yet another responsibility to a function whose name already doesn’t cover its current responsibilities, stop. Either rename the function to reflect its true scope, or — better — split it into functions whose names match their behaviors.

Don’t rename just for aesthetics. If the name is imprecise but nobody is confused by it, the cost of the rename (code review, merge conflicts, git blame noise) might exceed the benefit. Naming debt, like financial debt, can be carried strategically if the interest rate is low enough.

The Compound Clarity

There’s a compounding effect to good names that’s easy to underestimate.

When every function in a module accurately describes what it does, reading the module becomes almost trivial. You can scan the function signatures and understand the module’s behavior without reading any implementations. The names form a table of contents for the code.

When names are accurate, code review becomes faster. You read the function name, understand the intent, and can focus your review on whether the implementation matches. When names are misleading, you have to read the implementation first to understand what the function actually does, then evaluate whether it does it correctly. Every misleading name doubles the review burden.

When names are accurate, tests become more obvious. Testing sendConfirmationEmail means writing tests that verify an email is sent. Testing processOrder — which secretly sends emails — means either writing tests that seem out of scope or missing the email behavior entirely.

The Naming Ritual

I’ve developed a small habit: whenever I finish writing or modifying a function, I re-read the name and ask, “If I encountered this name with no context, would I correctly predict what the function does?”

Not approximately. Not with generous interpretation. Would the name alone give me an accurate mental model of the behavior?

If the answer is no, the function either needs a better name or needs to be split into smaller functions that can each have an accurate name. Often, the inability to name a function is a signal that the function is doing too much. The naming problem is really a decomposition problem in disguise.

Good names are not a luxury. They’re not polish applied after the real work is done. They’re load-bearing infrastructure — the mechanism by which code communicates its intent to every future reader.

Name things for the person who reads them next. They’ll arrive with no context, no history, just the name and a question: “What does this do?”

Make sure the name answers honestly.