---
title: "Naming things: a practical guide"
source: https://www.taim.io/learn-to-code/naming-things-a-practical-guide
published: Sun May 10 2026 14:29:14 GMT+0000 (Coordinated Universal Time)
updated: Thu Jun 11 2026 18:15:53 GMT+0000 (Coordinated Universal Time)
description: "A fast, concrete guide to naming variables and functions so they still make sense six months later, with three rules, bad-name smells, and safe rename habits."
---

# Naming things: a practical guide

Most 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.

Most 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.

## What you’ll be able to do after this

- Apply three concrete rules to improve most variable and function names in minutes.
- Run a small, safe rename refactor on your own code with clear feedback loops.
- Use AI naming suggestions as a brainstorming tool instead of a crutch.

## 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:

- comments repeat on every PR,
- “utils” and “helpers” spread quietly,
- and six months later, nobody remembers what `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

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

TypeScript

`// 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

TypeScript

`// 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

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

TypeScript

`// 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:

TypeScript

`// 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

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`/`res` in an Express handler, `ctx` in React or Next.js, `err` in 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:

TypeScript

`// 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

Let’s do an actual pass on your own code. Ten minutes, no architecture debates.

1. **Pick a function** you wrote in the last month, around 30-50 lines. Ideally something used in more than one place.
2. **Write one sentence**: “This function’s job is to…”. Don’t look at its current name while you do this.
3. **Apply the three rules** to just three things: the function name, its parameters, and 3-5 key variables.
4. **Run tests or type checks** to make sure you only changed names.

Example starting point:

TypeScript

`// 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:

TypeScript

`// 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:

TypeScript

`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:

- You can skim the file tomorrow and explain each function from the name alone.
- The call sites **read like sentences**: `await loadUserProfile(userId)`, `recordUserLogin(userId)`.
- Reviewers ask about behaviour, not “what does `data` mean here?”.

Weak signals, or signs you should retry:

- You need comments like `// this actually only handles premium users` because 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)

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:

1. **Scope it**: rename one function or one concept at a time. No drive-by cleanups across the whole repo.
2. **Use your tools**: rely on language-aware rename (your IDE, Cursor, VS Code, IntelliJ) rather than text search, especially in TypeScript, Java, or C#.
3. **Run checks**: run tests, type-checkers, or at least build the project. In a Next.js app, for instance, `npm test` or `npm run lint` will flush out missed imports.
4. **Scan 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:

1. 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. `
2. Ask for reasoning: Text`Explain in one sentence why each name fits the behaviour. `
3. 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

1. **Say what, not how**: name the job callers care about. Avoid implementation words like `fetchAndFilter` unless both are part of the contract.
2. **Prefer concrete to abstract**: pick domain nouns/verbs over “data/info/handle/process/manage”. `filterUsersByName` beats `processData`.
3. **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

1. Choose **one** function or concept.
2. Write its job in one plain sentence: “This function’s job is to…”.
3. Rename function + parameters + key locals using the three rules.
4. Use your IDE’s rename feature (not global text search).
5. Run tests / type checks / build.
6. 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:

1. Paste the function and describe behaviour: "This does X, Y, Z in our [domain]."
2. Ask: "Suggest 5 function names and explain why each fits using 1-2 sentences."
3. Follow-up: "Now give 3 more that avoid mentioning [implementation detail] and focus on what callers care about."
4. Pick or tweak a name only if you can defend it with: it says what, is concrete, and matches the function’s abstraction level.

### 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.

### Next steps: practice in your own codebase

- Today: run the 10-minute rename loop on one 30-50 line function you wrote recently. Apply the three rules and ship the change behind tests.
- This week: find one `utils` or `helper` file and split out a single, well-named module from it, even if the original still exists for now.
- In your next PR: add a short note on at least one function explaining why you picked its name using the three rules. Invite specific feedback instead of generic “rename?” comments.
- With AI: paste a messy function into your AI assistant and run the naming prompt from the cheatsheet. Compare its suggestions to your own and keep only the ones you can defend.
