- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Introduction — Why Naming Is So Hard
- A Name Should Reveal Intent, Not Implementation
- Name Length Scales with Scope
- Avoid Encodings and Hungarian Notation
- Use Searchable Names
- Conventions for Booleans, Functions, and Collections
- A Rename Is a Refactor of Its Own
- Common Pitfalls
- Wrapping Up
- References
Introduction — Why Naming Is So Hard
There is a famous quip attributed to Phil Karlton: "There are only two hard things in computer science: cache invalidation and naming things." The first time you hear it, it sounds like a clever bit of wordplay. Naming, as hard as cache invalidation? It's just sticking a word on a variable.
But after a few years of writing code, the joke starts to sting. Naming is hard not because typing is hard, but because the moment you name something, it becomes clear whether you actually understand the concept. If no good name comes to mind for a function, that is usually a signal that the function does more than one thing, or that you haven't yet defined its role clearly. Naming is a tool for thinking, not decoration.
This post lays out a few principles for good naming from a working engineer's perspective. The point is not to memorize rules; if you understand why each principle exists, you can make your own judgment call in unfamiliar situations.
A Name Should Reveal Intent, Not Implementation
If I could keep only one principle, it would be this: a good name says why something exists, not what it does mechanically. A reader should grasp the meaning of a value or function without having to read its implementation.
Look at a bad example.
# what is d? you have to read the implementation to know
d = 30
# iterate a list, keep the ones matching some condition, collect into a new list
list1 = []
for x in list2:
if x[3] == 1:
list1.append(x)
None of d, list1, list2, or x[3] == 1 speaks for itself. To understand this code you have to read all the surrounding context. Now rename it to reveal intent.
GRACE_PERIOD_DAYS = 30
active_users = []
for user in all_users:
if user.is_active:
active_users.append(user)
The logic didn't change; only the names did. Yet the code now explains itself. What the number 30 means, and why we're selecting these users, is carried in the names. There is no need for a comment saying "this is a 30-day grace period." A good name replaces a comment.
Name Length Scales with Scope
Beginners tend toward one of two extremes. One is shrinking everything to a, b, tmp. The other is the opposite: sentence-length names like theListOfAllActiveUsersInTheCurrentOrganization. Neither is good.
A solid rule is that the length of a name should be proportional to the size of its scope (how long it lives).
# short scope: a variable that lives only inside a one-line comprehension can be short
squares = [n * n for n in numbers]
# wide scope: a field used across a whole class, or a module global, should be descriptive
class PaymentProcessor:
def __init__(self):
self.pending_settlement_count = 0
A loop variable n that exists only within a single line is fine being short, because your eye can take in that tiny range at once. Conversely, a field that lives across an entire class being named n is a disaster. To trace where and how the value is used, the name has to carry its own context.
To summarize: loop indices and short lambda arguments can be short, function parameters moderate, and the wider and longer-lived the name, the more descriptive it should be. The wider the scope, the more explanatory weight a name has to bear.
Avoid Encodings and Hungarian Notation
Hungarian notation, popular once, prefixed variable names with type information: strName (string name), iCount (integer count), arrUsers (array of users). Today it is mostly regarded as an anti-pattern.
# Hungarian notation - avoid it
strUserName = "alice"
iRetryCount = 3
arrActiveUsers = [...]
# the type is known to the language and the IDE. let names focus on meaning
user_name = "alice"
retry_count = 3
active_users = [...]
There are two reasons. First, modern languages have a type system and an IDE that tell you the type. Duplicating the type in the name is redundant. Second, and more dangerous, the name starts lying when the type changes. If you later turn arrUsers into a set or a dictionary, the prefix arr becomes false information. A name should always be true, and encoding implementation details into a name makes that truth easy to break.
For the same reason, prefixing member variables with m_ or globals with g_ has largely fallen out of favor. If you truly need the distinction, syntax the language already provides, like self.count, is enough.
Use Searchable Names
Code spends far more time being read and searched than written. So whether a name is searchable (grep-able) is a more important practical criterion than it first appears.
Suppose you scattered the numeric literal 7 throughout the code. When you later realize this 7 means "one week" and want to change it, searching the whole project for 7 floods you with all sorts of unrelated sevens. But with a name, you can find exactly that one concept.
# bad: hard to find what this 7 means, or where else it appears
if days_since_login > 7:
send_reminder()
# good: searching by name turns up only where this concept is used
INACTIVE_THRESHOLD_DAYS = 7
if days_since_login > INACTIVE_THRESHOLD_DAYS:
send_reminder()
In the same vein, single-letter variables and overly common words obstruct search. If names like data, value, and item appear hundreds of times across a codebase, finding that specific value is hard. The more unique and specific a name is, the more it helps future-you.
Conventions for Booleans, Functions, and Collections
A few settled conventions are worth learning; they make an entire team's code easier to read.
Booleans should read as a yes/no question. Prefixes like is, has, can, and should make them read naturally in conditionals.
# the name alone becomes a true/false question
is_active = True
has_permission = user.role == "admin"
can_retry = attempt < max_attempts
if is_active and has_permission:
...
Left as nouns like active or permission, if active: is ambiguous between "is it active?" and "make it active." A single prefix removes that ambiguity.
Function names start with a verb, because a function is an action: getUser, calculateTotal, sendEmail. Variables that hold values, by contrast, are nouns. It also helps when the name hints at what is returned. A reader naturally expects getUser() to return a user and isValid() to return a boolean.
Collections are plural. A list or set that holds many items gets a plural name.
# singular is one, plural is many - reads naturally when iterating
users = fetch_all_users()
for user in users:
notify(user)
user is one, users are many. Thanks to this simple rule, for user in users: reads like a sentence. By contrast, code like for u in userList: makes you pause and decode it every time.
A Rename Is a Refactor of Its Own
This is where many people get stuck. "I know the name isn't great, but changing it means touching things all over..." And so bad names stay fossilized in place.
But a rename is one of the safest refactors available in modern tools. An IDE's "rename symbol" feature is not a plain string replace; it understands the language and changes only the places where that symbol is referenced. It leaves unrelated variables with the same name untouched.
The principle is simple: the moment a better name occurs to you is the moment to change it. It's best to keep a rename commit separate from logic changes. That way a reviewer can see at a glance that "this is a pure rename and the behavior is unchanged."
good habit:
commit 1: rename calc() -> calculate_monthly_total() (no behavior change)
commit 2: the actual logic change (behavior changes)
bad habit:
commit 1: rename and change logic and move a field... (unreviewable)
Do not be afraid to fix a name. Code is living documentation that earns better names as your understanding deepens. You don't have to nail the perfect name up front. Let the name grow as your understanding grows.
Common Pitfalls
Finally, a few naming pitfalls you'll meet often in practice.
- Names that lie: If a function is named
getUserbut internally creates or deletes a user, the name is lying. A lookup function is expected to have no side effects, so this kind of betrayal is especially dangerous. - Inconsistent vocabulary: If the same concept is
fetchin one place,getin another, andretrievein a third, readers wonder "are these three different?" Within one codebase, pick one word per concept and stick with it. - Meaningless noise words: In
UserData,UserInfo, andUserObject, theData,Info, andObjectadd nothing. Often plainUseris enough. - Unpronounceable names: A name like
genymdhms(generation year month day hour minute second) can't be said aloud when you talk to a colleague. Code is something a team discusses together, so pronounceability matters too.
Wrapping Up
Naming is "the hardest easy skill" because there are no syntactic constraints on it, yet it lays bare exactly how well you understand the problem. A good name states intent rather than implementation, has a length proportional to its scope, doesn't encode types, is searchable, and follows the team's conventions.
Above all, names are not fixed. When a better one occurs to you, a rename is a safe refactor you can do anytime. Rather than straining to squeeze out the perfect name from the start, it's far healthier to name something "the best I can with my current understanding" and refine it each time your understanding deepens. In the end, when well-chosen names accumulate, the code becomes prose that reads without a separate manual.
References
- Robert C. Martin, "Clean Code" (Chapter 2: Meaningful Names)
- Background on the Phil Karlton quote: https://www.karlton.org/2017/12/naming-things-hard/
- Tim Ottinger, "Ottinger's Rules for Variable and Class Naming"
- Kernighan & Pike, "The Practice of Programming" (clarity and naming)