Naming things: a practical guide
ost code review comments are about naming, not algorithms. The work is not coming up with clever words, it’s choosing clear ones that still make sense when you’ve forgotten how the code works.
Practical naming guide
- Naming code clearlycontext
- Review fixates on names
- Check your starting point
- Say what, not how
- Prefer concrete names
- Match abstraction level
- Practice rename loopthen
- Read feedback signals
Article mapOpen the visual summary
Practical naming guide
- Naming code clearlycontext
- Review fixates on names
- Check your starting point
- Say what, not how
- Prefer concrete names
- Match abstraction level
- Practice rename loopthen
- Read feedback signals
Table of Contents10 sections
- What you’ll be able to do after this· 1 min
- Why naming dominates review, but rarely improves· 1 min
- Quick self-check: what’s your naming starting point?· 1 min
- Rule 1: Say what, not how· 1 min
- Rule 2: Prefer concrete to abstract· 1 min
- Rule 3: Match the level of abstraction· 1 min
- Bad patterns in the wild: utils, helpers, managers, processors· 1 min
- Abbreviations: when they help, when they quietly hurt· 1 min
- First practice loop: rename a real function· 1 min
- Reading feedback: good vs poor signals· 1 min
What you’ll be able to do after this
Why naming dominates review, but rarely improves
Naming is easy to argue about and hard to practice. Reviews gravitate to it because it’s the first thing you notice when you read unfamiliar code.
The problem: most teams treat naming as taste, not a skill. People say “this name feels off” without a shared checklist for what better looks like.
Long term, that means:
processData really processes.This guide is deliberately practical. We’ll use three rules, work through real-feeling renames, and end with a short practice loop you can run on your own code this week.
Quick self-check: what’s your naming starting point?
Before rules, place yourself.
You’re probably in one of these modes:
- Label mode: you slap on the first name that compiles. Plenty of
x,data,handleClick. You rely on comments to explain what’s going on. - Vibe mode: you try to name things well but rely on feeling, not criteria. Reviewers often say “rename?” without clear guidance.
- Deliberate mode: you already pause to rename after code works. You still get stuck on bigger functions or cross-cutting stuff.
If you’re in label or vibe mode, focus hard on the examples and the first practice loop. If you’re already deliberate, skim for gaps, then jump to the refactor and AI sections to tighten your habits.
Rule 1: Say what, not how
Bad names
- implementation details
- how today’s version happens to get it
- imagined caller flow
- arr
- term
- fetchAndFilter
Good names
- observable job
- what they get
- the real job
- users
- nameQuery
- filterUsersByName
A good name describes the observable job, not the internal trick.
Bad names latch onto implementation details that are likely to change. Good names describe the effect a caller cares about.
A name ages well when it tells the next reader what they get, not how today’s version happens to get it. Implementation details are cheap to change; caller expectations are expensive. Name the expectation.
Worked rename
// Before
function fetchAndFilter(arr: User[], term: string) {
return arr.filter((u) => u.name.includes(term));
}
// After
function filterUsersByName(users: User[], nameQuery: string) {
return users.filter((u) => u.name.includes(nameQuery));
}
What changed:
fetchAndFilter→filterUsersByName: The function doesn’t fetch anything. The old name leaked an imagined caller flow, not the real job.arr→users: Callers care that these are users, not generic arrays.term→nameQuery: Now the parameter explains which field it targets.
The code didn’t change; only expectations became explicit.
When you’re stuck, finish this sentence out loud: “This function’s job is to…”. Your name should be a short version of that sentence’s verb + key nouns.
Rule 2: Prefer concrete to abstract
Vague names feel reusable. In practice, they just hide what’s really going on.
Abstract words (data, info, handle, process, manage) usually mean you haven’t decided what something actually is yet.
Worked rename
// Before
function handleData(input: any) {
const result = doStuff(input);
save(result);
}
// After
function enrichOrderWithPricing(order: Order) {
const pricedOrder = calculateOrderTotals(order);
saveOrder(pricedOrder);
}
Why this is better:
handleData→enrichOrderWithPricing: The real job is “take an order and attach prices/totals”. That’s what future readers need to know.input→order,result→pricedOrder: You’ve named the domain, not the shape.doStuff→calculateOrderTotals,save→saveOrder: Each function now declares which concept it touches.
Concrete names feel narrower, but they let you see missing abstractions. If you later need to enrich something else, you can extract a reusable enrichWithPricing that works across entities (on purpose, not by accident).
Rule 3: Match the level of abstraction
Split responsibilities
- Option A
- two focused functions
- higher-level place
- split responsibilities
- recordUserLogin
Expose both responsibilities
- Option B
- longer name
- smallest helpful unit
- expose both responsibilities
- recordUserLoginAndWelcome
The name should sit at the same altitude as the function’s body.
If the name sounds high-level but the body is a mess of details, callers feel tricked. If the name is gritty but the body just delegates, you leak internal structure.
Worked rename
// Before
function updateUser(id: string) {
const user = usersRepo.find(id);
user.lastLoginAt = new Date();
usersRepo.save(user);
}
// After
function recordUserLogin(userId: string) {
const user = usersRepo.find(userId);
user.lastLoginAt = new Date();
usersRepo.save(user);
}
Here, the function does one specific thing: it records a login. updateUser is much broader and creates false expectations.
Another common case:
// Before
function recordUserLoginAndSendWelcomeEmail(userId: string) {
const user = usersRepo.find(userId);
user.lastLoginAt = new Date();
usersRepo.save(user);
emailClient.sendWelcome(user.email);
}
// Two better options
function recordUserLogin(userId: string) { /* ... */ }
function recordUserLoginAndWelcome(userId: string) { /* ... */ }
Option A: split responsibilities and call two focused functions from a higher-level place. Option B: if this really is the smallest helpful unit, let the longer name expose both responsibilities.
Mismatch is a smell. If the body reads like a script, the name should too. If you only glue together two other calls, the name should describe the glue-level idea, not the innards of each call.
Bad patterns in the wild: utils, helpers, managers, processors
Certain words are magnets for confusion. They grow until they mean nothing.
Here’s how they usually show up, and what to do instead.
| Smell | Typical symptom | Better move |
|---|---|---|
utils.ts |
1000+ lines of unrelated functions | Split by domain: dateFormatting.ts, urls.ts |
helper.ts |
Functions used from one place, “just in case” reuse later | Inline or give it a specific job name |
*Manager |
Class that does fetching, validation, logging, side effects | Extract narrower services: UserLoader, etc. |
*Processor |
Vague long-running job or pipeline of mixed steps | Name the pipeline: InvoicePaymentRunner |
These words are not banned. They’re last resort labels once you’ve tried to name the real concept and failed.
If you must keep them (legacy code, shared libs), at least pair them with a domain word: EmailTemplateHelpers, ImageResizingUtils. That gives future you a better starting point when you eventually split things up.
Abbreviations: when they help, when they quietly hurt
Harder to read
- calcPrRev
- prc
- getPrc
- qty
- invented to save characters
Clear enough
- calculateProjectedRevenue
- price
- getPriceForSku
- quantity
- spell it out
Abbreviations are not evil. The problem is unsignaled, personal abbreviations.
In many codebases, you’ll see things like cfg, usr, req, res, ctx. Some of these are fine; some are slow traps.
Use this rule of thumb:
- If an abbreviation is standard in your language or framework, it’s usually okay:
req/resin an Express handler,ctxin React or Next.js,errin Node callbacks. - If it’s domain jargon that everyone on the team uses daily, it’s often fine:
SKU,UUID,VAT. - If you invented it just to save characters, it will hurt you later.
Compare:
// Harder to read
function calcPrRev(sku: string, qty: number) {
const prc = getPrc(sku);
return prc * qty;
}
// Clear enough
function calculateProjectedRevenue(sku: string, quantity: number) {
const price = getPriceForSku(sku);
return price * quantity;
}
The second is longer but not bloated. Each extra character pays for itself the first time someone else reads the file.
When in doubt, spell it out. You have autocomplete. Future readers don’t.
First practice loop: rename a real function
- Pick a function
- Write one sentence
- Apply the three rules
- Run tests or type checks
- only changed names
Let’s do an actual pass on your own code. Ten minutes, no architecture debates.
- 1Pick a function you wrote in the last month, around 30-50 lines. Ideally something used in more than one place.
- 2Write one sentence: “This function’s job is to…”. Don’t look at its current name while you do this.
- 3Apply the three rules to just three things: the function name, its parameters, and 3-5 key variables.
- 4Run tests or type checks to make sure you only changed names.
Example starting point:
// Before
function process(data: any) {
const res = api.fetch(data.id);
if (!res.ok) return null;
const out = transform(res.payload);
log(out);
return out;
}
Attempted rename:
// After, first pass
function fetchAndTransformUserProfile(userId: string) {
const response = api.fetch(userId);
if (!response.ok) return null;
const profile = transform(response.payload);
log(profile);
return profile;
}
We improved data to userId and res/out to response/profile. The function name is better, but still noisy and a bit how-ish.
On a second, more honest pass:
function loadUserProfile(userId: string) {
const response = api.fetch(userId);
if (!response.ok) return null;
const profile = transform(response.payload);
log(profile);
return profile;
}
Now the name matches the level of abstraction: the caller cares that they can load a user profile, not that it “fetches and transforms”.
Reading feedback: good vs poor signals
Once you do that rename pass, pay attention to the feedback (from yourself and others).
Strong signals that the rename was worth it:
await loadUserProfile(userId), recordUserLogin(userId).data mean here?”.Weak signals, or signs you should retry:
- You need comments like
// this actually only handles premium usersbecause the name hides constraints. - People suggest alternative names in review without agreeing on why yours is wrong or right.
- You still feel the need to open the function body to remember what it returns.
When feedback is vague (“this name feels off”), pull it into specifics: ask “what did you expect this to do from the name alone?”. Their answer is gold for your next rename.
Renaming as a refactor habit (and how to do it safely)
- 1
Scope it
Rename one function or one concept at a time.
- 2
Use your tools
Rely on language-aware rename rather than text search.
- 3
Run checks
Run tests, type-checkers, or at least build the project.
- 4
Scan the diff
Read the diff like a stranger.
Renaming is one of the lowest-risk refactors you can do, if you keep the surface area small.
Here’s a simple protocol you can run in a live codebase in under ten minutes:
- 1Scope it: rename one function or one concept at a time. No drive-by cleanups across the whole repo.
- 2Use your tools: rely on language-aware rename (your IDE, Cursor, VS Code, IntelliJ) rather than text search, especially in TypeScript, Java, or C#.
- 3Run checks: run tests, type-checkers, or at least build the project. In a Next.js app, for instance,
npm testornpm run lintwill flush out missed imports. - 4Scan the diff: read the diff like a stranger. Do the new names make the code read more like your mental model of the system?
If you don’t have tests, start with the most local functions (ones used in a single module). For critical flows, consider adding at least one basic test or logging check before renaming.
And don’t wait for perfect. The habit you want is: code works → quick pass to rename → then push. Two minutes now saves dozens of tiny puzzles later.
Working with AI: suggestions without outsourcing judgement
AI pair programmers are surprisingly good at generating name ideas. They’re also happy to suggest misleading names if your description is vague.
The trick is to use them as brainstorming partners, not oracles.
A simple pattern that works well in tools like Cursor, Claude Code, or GitHub Copilot Chat:
-
Describe behaviour first:
Text This function takes a userId, fetches their profile from Supabase, normalizes it, and caches it for 5 minutes. Suggest 5 function names. -
Ask for reasoning:
Text Explain in one sentence why each name fits the behaviour. -
Critique the answers like you would a teammate’s PR. If a name hides constraints (like caching) or over-emphasizes implementation (like Redis vs generic cache), push back:
Text These focus too much on Redis. Suggest alternatives that a caller in another module would understand without knowing the caching layer.
You’ll know you’re over-trusting AI when you accept names you wouldn’t be able to defend in review. If you can’t explain why a suggested name is good using the three rules, it probably isn’t.
Naming variables and functions: field reference
Three rules at a glance
- Say what, not how: name the job callers care about. Avoid implementation words like
fetchAndFilterunless both are part of the contract. - Prefer concrete to abstract: pick domain nouns/verbs over “data/info/handle/process/manage”.
filterUsersByNamebeatsprocessData. - Match the abstraction level: if the body is a single domain action, the name should be that action; if it’s a flow of multiple steps, either split it or let the name admit all the responsibilities.
⚡ Bad-name smells
Watch for these as immediate refactor candidates: utils.ts over ~200 lines; helper.ts used from only one place; any Manager that knows about HTTP, DB, and business rules; Processor that handles more than one entity type; variables named data, info, res, obj outside of tiny local scopes; functions named only with verbs like handle, run, process with no domain nouns attached.
Quick rename protocol
- Choose one function or concept.
- Write its job in one plain sentence: “This function’s job is to…”.
- Rename function + parameters + key locals using the three rules.
- Use your IDE’s rename feature (not global text search).
- Run tests / type checks / build.
- Skim the diff: if a new reader can follow the story from names alone in under 30 seconds, you’re done. If not, iterate once more, then stop.
Abbreviation decisions
Keep abbreviations when they are: (a) language/framework norms (req, res, ctx), or (b) stable domain terms (SKU, VAT, UUID). Spell out everything else, especially cross-team concepts. A practical rule: if a new hire would need to ask what the abbreviation means, write the full word. Avoid half-abbreviations like prc or usr entirely; they save 1-2 keystrokes and cost minutes of reading time.
⚡ AI naming prompt
Use this pattern:
- Paste the function and describe behaviour: "This does X, Y, Z in our [domain]."
- Ask: "Suggest 5 function names and explain why each fits using 1-2 sentences."
- Follow-up: "Now give 3 more that avoid mentioning [implementation detail] and focus on what callers care about."
- Pick or tweak a name only if you can defend it with: it says what, is concrete, and matches the function’s abstraction level.
Want a more guided way to practice this?
Naming FAQ
How long should a function name be?
Use as many words as you need to be clear, but no more. For most business code, 2-5 words is a good range: loadUserProfile, calculateInvoiceTotals, recordUserLoginAndWelcome. Short but vague is worse than slightly longer and specific. If your name is stretching past 6-7 words, that’s often a smell that the function is doing too much, not that the name is too long. In that case, try splitting the function into smaller pieces rather than compressing the name into something cryptic.
When are abbreviations okay in names?
Abbreviations are fine when they’re part of the shared language of your stack or your domain. Things like req, res, ctx, and err are so common in JavaScript/TypeScript backends that spelling them out doesn’t buy much. Likewise, teams that live in e‑commerce will understand SKU and VAT instantly. Where they hurt is in custom, personal short forms, usr, cfg, prc, itm, that only made sense to the original author. A practical rule: if the benefit is just saving a couple of characters, don’t abbreviate; if the benefit is aligning with a widely-known term, it’s usually safe.
Should boolean variables always start with "is"?
They don’t have to, but they do need to read like yes/no questions. Prefixes like is, has, can, should, and needs all work well because they make conditions read clearly: if (isAdmin), if (hasSubscription), if (canRetry). The key is consistency within a codebase. Avoid ambiguous booleans like active or flag with no context; prefer isActive or isFeatureFlagEnabled. Reserve bare adjectives like primary or disabled for UI class names or config objects where the semantics are obvious from context.
⚠️ How do I rename safely in a live codebase?
Treat renaming as a small, scripted refactor. Start by scoping tightly: one function, one class, or one file at a time. Use your IDE’s symbol-aware rename so it catches imports, references, and type declarations instead of relying on global find-and-replace. After the rename, run automated checks: tests, linters, type-checkers, to catch anything you missed. Finally, read your diff like a stranger: are there any places where the new name feels wrong or surprising? If you’re nervous, ship renames in their own pull request with no logic changes, so reviewers can focus purely on the naming change.
Naming is a skill, not a vibe
Naming is where your code communicates. Compilers don’t care what you call things, but every future reader does, and that includes you, three months from now.
You don’t need a grand theory of naming to get better. You need a few rules, small practice loops, and the willingness to rename once the code works.
Use “say what, not how”, “prefer concrete to abstract”, and “match the abstraction level” as your working checklist. Add a quick rename pass before you push. Treat AI as a helpful reviewer, not a replacement for thinking.
Over time, your functions will read more like documentation than puzzles. That’s the point: code that tells the truth about what it does, in words your future teammates can actually understand.