Naming is the single highest-leverage writing skill in software engineering. A good name collapses the entire mental model of a thing into a word or two. A bad name sends every reader on a detour to figure out what the thing actually is. In 2026, AI-assisted coding makes naming even more important — a well-named codebase produces far better autocomplete and explanation quality than a soup of abbreviations.
What changed in 2026
- LLM pair programmers index on names. GitHub Copilot, Cursor, and similar tools use identifiers as strong signals for suggestion quality. Descriptive names produce dramatically better completions.
- Type inference reduced the need for type-in-name. TypeScript, Python type hints, and Rust make
strUserName redundant — the type is already visible in the signature.
- Linters now flag common naming smells. ESLint, Ruff, and Clippy have grown rules that catch single-letter variables outside loops and non-question-form booleans.
- Code search is ubiquitous. Everyone uses semantic search now. Descriptive names are searchable;
tmp2 is not.
The fundamental rule: reveal intent
The name should tell a reader what the value is, not what data structure holds it or how it was computed.
# Bad — mechanics, not intent
d = {}
lst = []
tmp = get_data()
val = user.age * 365
# Good — intent revealed
users_by_email = {}
pending_orders = []
raw_config = get_data()
age_in_days = user.age * 365
Scope-based verbosity
Longer scope deserves longer names. The rule of thumb: name length should scale with the distance between declaration and last use.
// Fine: 3-line lambda, 'u' is obvious from context
const names = users.map(u => u.name);
// Not fine: 80-line function, 'u' forces the reader to scroll back
function processUsers(u: User[]) {
// 40 lines later — is 'u' still users? filtered users? one user?
}
// Better
function processUsers(activeUsers: User[]) { ... }
Naming by category
Booleans
Always a question: is*, has*, can*, should*, was*.
// Bad
let loading = true;
let error = false;
let editMode = false;
let active2 = true;
// Good
let isLoading = true;
let hasError = false;
let canEdit = false;
let isActive = true;
Functions and methods
Use a verb + object form. The verb describes the action.
| Pattern |
Examples |
get* — returns a value, no side effects |
getUserById, getTotal |
fetch* — async I/O |
fetchOrders, fetchUserProfile |
set* — mutates a single value |
setPageTitle, setStatus |
update* — partial mutation |
updateUserEmail |
create* / build* |
createInvoice, buildQuery |
handle* — event handler |
handleSubmit, handleClick |
is* / has* / can* — predicate |
isExpired, hasPermission |
Collections
Always plural and named by what they contain, not their structure:
# Bad
user_list = []
data = {}
items = set()
# Good
pending_users = []
config_by_env = {}
seen_ids = set()
Constants
SCREAMING_SNAKE_CASE for true constants. Do not use it for values that may change per environment.
const MAX_RETRY_ATTEMPTS = 3; // true constant
const DEFAULT_PAGE_SIZE = 25; // true constant
const API_BASE_URL = process.env.API_URL; // NOT a constant — it varies
The naming table
| Situation |
Bad |
Good |
| User data map |
map, d, userData |
usersByEmail, usersById |
| Error boolean |
error, err2, flag |
hasError, isNetworkError |
| Async state |
loading, fetching |
isLoading, isFetching |
| Event handler |
fn, handler, click |
handleAddToCart, onFormSubmit |
| Loop counter |
i (fine in a 3-line loop) |
rowIndex, pageNumber if loops nest |
| Temporary result |
tmp, res, data |
filteredUsers, parsedResponse |
How to pick names under pressure
- Say it aloud. If you would not say "pass me the
isActiveUsers" in conversation, the name is off.
- Avoid noise words.
userInfo, dataObject, managerHelper — these add length without meaning. Drop the suffix.
- When in doubt, longer is better.
accountDeletionScheduledAt > deletedAt. You can always shorten later; you cannot regain lost context.
- Domain language first. Match your naming to the business domain (invoices, subscriptions, cohorts), not the technical layer (controllers, handlers, processors).
Common mistakes
Re-using names in nested scopes. A user variable in the outer scope shadowed by a user parameter in a callback is a silent bug waiting to happen.
Abbreviating inconsistently. usr, user, u, usr_obj for the same concept in the same file. Pick one and lint for it.
Naming after the type. userArray, configObject, callbackFunction — the type annotation already says this; the name should say which users, which config.
Gerunds for nouns. processing, loading, filtering as variable names — these sound like actions, not values. If it is a boolean, use isProcessing. If it is a list, use processedItems.
What to skip
- Hungarian notation.
strName, iCount, bIsValid — dropped by most style guides since 2010; type systems make it redundant.
- Single-letter names outside a 5-line math/loop context.
n, x, t deep in business logic.
- Comments that explain a bad name.
let d = {}; // dictionary of users by email — just rename it.
FAQ
Should I rename things as I read code?
Yes, if you can run the tests after. A rename-with-passing-tests is always welcome in a code review.
What about names that are long and unwieldy?
If a name needs 5+ words to be clear, the concept it represents may need to be extracted into its own type or function.
How do I name a function that does two things?
You found a bug in the design, not the name. Split the function.
What about names in SQL?
Same rules. u as a table alias in a 40-join query is a nightmare. Use users u and reference u.email — that is acceptable abbreviation.
Where to go next