We all know that naming is one of the hardest problems in programming, and most of us have probably written code like this when we just started programming:
I wrote this code more than 20 years ago in Delphi, and, honestly, I don’t really remember what the app was supposed to do. It has it all: single-character names (i, j, E), abbreviations (FormatsCnt, buf), acronyms (sr, fp), and a mix of different naming conventions. It has some comments, though! (And I kept the original indentation for complete immersion.)
I once worked with a very seasoned developer who mostly used very short names and never wrote any comments or tests. Working with their code was like working with Assembler — it was very difficult. Often, we wasted days tracking and fixing bugs.
Let’s look at these (and many other) naming antipatterns and how to fix them.
Function calls with multiple parameters can be hard to understand, when there are too many of them or some are optional. Consider this function call:
This null in the middle is grotesque — who knows what was supposed to be there and why it’s missing…
However, the worst programming pattern of all time is likely positional boolean function parameters:
What are we disabling here? It’s impossible to answer without reading the appendScriptTag() function’s code.
How many parameters are too many? In my experience, more than two parameters are already too many. Additionally, any boolean parameter is automatically too many.
Some languages have named parameters to solve these problems. For example, in Python we could write this:
It’s obvious what the code above does. Names serve as inline documentation.
Unfortunately, JavaScript doesn’t support named parameters yet, but we can use an object instead:
The code is slightly more verbose than in Python, but it achieves the same outcome.
I can say a lot about this code, but let’s focus on this line first:
The double negation, “if not no errors found…”, makes my brain itch, and I almost want to take a red marker and start crossing out !s and nos on my screen to be able to read the code.
In most cases, we can significantly improve code readability by converting negative booleans to positive ones:
Positive names and positive conditions are usually easier to read than negative ones.
By this time, we should notice that we don’t need the errorsFound variable at all: its value can be derived from the errorMessages array — errors found when we have any error messages to show:
Here’s another example:
Again, every time we read noData in the code, we need to mentally unnegate it to understand what’s really happening. And the negative disabled attribute with double negation (!noData) makes things even worse. Let’s fix it:
Now, it’s much easier to read.
Info We talk about names like data later in this chapter.
My rule of thumb is that the shorter the scope of a variable, the shorter its name should be.
I generally avoid very short variable names, but I prefer them for one-liners. Consider this example:
It’s clear what x is, and a longer name would bloat the code without making it more readable, likely less. We already have the full name in the parent function: we map over a list of breakpoints and convert numbers to strings. It also helps that here we only have a single variable, so any short name will be read as “whatever we map over.”
I usually use x in such cases. I think it’s clear enough that it’s a placeholder and not an acronym for a particular word, and it’s a common convention.
Some developers prefer _, and it’s a good choice for any programming language except JavaScript, where _ is often used for the Lodash utility library.
Another convention I’m okay with is using a/b names for sorting and comparison functions:
Loop indices i, j, and k are some of the most common variable names ever. They are moderately readable in short, non-nested loops, and only because programmers are so used to seeing them in the code:
Info I used longer names for index variables, like somethingIdx, for a very long time. Surely, it’s way more readable than i, but, luckily, most modern languages allow us to iterate over things without coding artisan loops and without the need for an index variable. We talk more about this in the Avoid loops chapter.
However, in nested loops, it’s difficult to understand which index belongs to which array:
It’s difficult to understand what’s going on here because variables i and j have no meaning. It works for non-nested loops, where i means “whatever the array contains,” but for nested arrays and loops, it’s not clear enough.
In the end, x, a, b, and i are pretty much all single-character names I ever use.
However, when the scope is longer or when we have multiple variables, short names can be confusing:
In the code above, a and b are okay (we talked about them earlier), but d0, al, and bl make this code more complex than it should be.
Let’s try to improve it a bit:
Now, it’s clearer what the code is doing, and the comments explain the high-level idea instead of repeating the code.
On the other hand, long names in a short scope make the code cumbersome:
We can shorten the names to make the code more readable:
We talked about the scope in the previous section. A variable’s scope size affects readability too. The shorter the scope, the easier it is to keep track of a variable.
The extreme cases would be:
One-liner functions, where the scope of a variable is a single line: easy to follow (example: [8, 16].map(x => x + 'px')).
Global variables, whose scope is infinite: a variable can be used or modified anywhere in the project, and there’s no way to know which value it holds at any given moment, which often leads to bugs. That’s why many developers have been advocating against global variables for decades.
Usually, the shorter the scope, the better. However, extreme scope shortening has the same issues as splitting code into many teeny-tiny functions: it’s easy to overdo it and hurt readability.
I found that reducing the lifespan of variables works as well and doesn’t produce lots of tiny functions. The idea here is to reduce the number of lines between the variable declaration and the line where the variable is accessed for the last time. A variable’s scope might be a whole 200-line function, but if the lifespan of a particular variable is three lines, then we only need to look at these three lines to understand how this variable is used.
In the code above, the lifespan of the sorted variable is only two lines. This kind of sequential processing is a common use case for this technique.
Tip Double-click on a variable name to select all its appearances in the code. This helps to quickly see the variable’s lifespan.
Info See a larger example in the Avoid Pascal-style variables section in the Avoid reassigning variables chapter.
Magic numbers are any numbers that might be unclear to the code reader. Consider this example:
A seasoned developer would likely guess that 3600 is the number of seconds in an hour, but the actual number is less important to understand what this code does than the meaning of this number. We can make the meaning clearer by moving the magic number into a constant:
I also like to include a unit in a name if it’s not obvious otherwise:
A perfect example where constants make code more readable is days of the week:
Is 6 a Saturday, Sunday, or Monday? Are we counting days from 0 or 1? Does the week start on Monday or Sunday?
Defining constants for these values makes it clear:
Another common use case for magic numbers, which is somehow widely accepted, is HTTP status codes:
I know what the 404 status is, but who remembers what the 429 status means?
Let’s replace the magic numbers with constants:
Now, it’s clear which status codes we’re handling.
Personally, I’d use a library like http-status-codes here if I needed to work with status codes often or use not-so-common codes:
However, having a clear name is sometimes not enough:
In the code above, we remove the time portion of a string containing a date and time in the ISO format (for example, 2023-03-22T08:20:00+01:00) by keeping only the first ten characters — the length of the date part. The name is quite clear, but the code is still a bit confusing and brittle. We can do better:
Now, it’s easier to visualize what the code does, and we don’t need to count characters manually to be sure that The Very Magic number 10 is correct.
Code reuse is another good reason to introduce constants. However, we need to wait for the moment when the code is actually reused.
Sometimes, programmers replace absolutely all literal values with constants, ideally stored in a separate module:
However, not every value is magic; some values are just values. Here, it’s clear that the value is the width of the ID column, and a constant doesn’t add any information that’s not in the code already. Instead, it makes the code harder to read: we need to go to the constant definition to see the actual value.
Often, code reads perfectly even without constants:
In the code above, it’s clear that the minimum width of a modal is 50vw. Adding a constant won’t make this code any clearer:
I avoid such constants unless the values are reused.
Sometimes, such constants are misleading:
The ID_COLUMN_WIDTH name is imprecise: it says that the value is the width, but it’s the minimum width.
Often, zeroes and ones aren’t magic, and code is easier to understand when we use 0 and 1 directly instead of constants with inevitably awkward names:
This function returns the last second of a given date. Here, 1 and -1 really mean next and previous. They are also an essential part of the algorithm, not a configuration. It doesn’t make sense to change 1 to 2 because it will break the function. Constants make the code longer and don’t help us understand it. Let’s remove them:
Now, the code is short and clear, with enough information to understand it.
These constants are related — they define different values of the same scale, size of something, and are likely to be used interchangeably. However, it’s not clear from the names that they are related. We could add a suffix:
Now, it’s clear that these values are related, thanks to the _SIZE suffix. But we can do better:
The common part of the names, the SIZE_ prefix, is aligned, making it easier to notice related constants in the code.
Another option is to use an object:
It has some additional benefits over separate constants:
We only need to import it once (import { Size } from '...' instead of import { SIZE_SMALL, SIZE_MEDIUM } from '...').
Better autocomplete after typing Size.
However, my favorite approach is to use a TypeScript enum:
Tip Usually, enum names are singular nouns in PascalCase, like Month, Color, OrderStatus, or ProductType.
Which is essentially the same as an object, but we can also use it as a type:
This gives us better type checking and even better autocomplete. For example, we can define separate types for button sizes and modal sizes, so the button component will only accept valid button sizes.
The road to hell is paved with abbreviations. What do you think OTC, RN, PSP, or SDL mean? I also don’t know, and these are just from one project. That’s why I try to avoid abbreviations almost everywhere, not just in code.
I’d even go further and create a list of approved abbreviations. I could only find one example of such a list — from Apple — and I think it could be a great start.
Common abbreviations are okay; we don’t even think of most of them as abbreviations:
Abbreviation
Full term
alt
alternative
app
application
arg
argument
err
error
info
information
init
initialize
max
maximum
min
minimum
param
parameter
prev
previous (especially when paired with next)
As well as common acronyms, such as:
HTML;
HTTP;
JSON;
PDF;
RGB;
URL.
And possibly a few very common ones used on a project, but they still should be documented (new team members will be very thankful for that!) and shouldn’t be ambiguous.
I like to use the following prefixes for function names:
get: returns a value (example: getPageTitle).
set: stores a value or updates React state (example: setProducts)
fetch: fetches data from the backend (example: fetchMessages).
reset: resets something to its initial state (example: resetForm).
remove: removes something from somewhere (example: removeFilter).
to: converts the data to a certain type (examples: toString, hexToRgb, urlToSlug).
on and handle for event handlers (examples: onClick, handleSubmit).
Info Verb prefixes are also called actions in the A/HC/LC pattern. See more in the Use the A/HC/LC pattern section later in this chapter.
And the following prefixes for boolean variables or functions that return a boolean value:
is, are, has, or should (examples: isPhoneNumberValid, hasCancelableTickets).
These conventions make code easier to read and distinguish functions that return values from those with side effects.
Tip Don’t combine get with other prefixes: I often see names like getIsCompaniesFilterDisabled or getShouldShowPasswordHint, which should be just isCompaniesFilterDisabled or shouldShowPasswordHint, or even better isCompaniesFilterEnabled. On the other hand, setIsVisible is perfectly fine when paired with isVisible.
I also make an exception for React components, where I prefer to skip the is prefix, similar to HTML properties like <button disabled>:
In general, I don’t like to remember too many rules, and any convention can go too far. A good example, and fortunately almost forgotten, is Hungarian notation, where each name is prefixed with its type, or with its intention or kind. For example, lAccountNum (long integer), arru8NumberList (array of unsigned 8-bit integers), usName (unsafe string).
Hungarian notation made sense for old untyped languages like C, but with modern typed languages and IDEs that show types when you hover over the name, it clutters the code and makes reading each name harder. So, keep it simple.
One of the examples of Hungarian notation in the modern frontend is prefixing TypeScript interfaces with I:
Luckily, most TypeScript developers prefer to drop it these days:
I would generally avoid repeating information in the name that’s already accessible in its type, class name, or namespace.
Info We talk more about conventions in the Code style chapter.
Often, we need to create a new value based on the previous value of a certain variable or object.
Consider this example:
In the code above, we have a basic counter function that returns the next counter value. The prev prefix makes it clear that this value is out of date.
Similarly, when we need to store the new value in a variable, we can use the next prefix:
Both conventions are widely used by React developers.
Incorrect names are worse than magic numbers. With magic numbers, there’s a possibility of making a correct guess, but with incorrect names, we have no chance of understanding the code.
Consider this example:
Even a comment doesn’t help us understand what this code does.
What’s actually happening here is that the getTime() function returns milliseconds while the getTimezoneOffset() returns minutes, so we need to convert minutes to milliseconds by multiplying minutes by the number of milliseconds in one minute. 60000 is exactly this number.
Let’s correct the name:
Now, it’s much easier to understand the code.
Info Underscores (_) as separators for numbers were introduced in ECMAScript 2021 and make long numbers easier to read: 60_000 instead of 60000.
Types often make incorrect names more noticeable:
By looking at the types, it’s clear that both names should be plural (they contain arrays), and the selectedOrder only contains order IDs, not whole order objects:
We often change the logic but forget to update the names to reflect that. This makes understanding the code much harder and can lead to bugs when we later change the code and make incorrect assumptions based on incorrect names.
Abstract and imprecise names are less dangerous than incorrect names. However, they are unhelpful and make the code harder to understand.
Abstract names are too generic to give any useful information about the value they hold:
data;
list;
array;
object.
The problem with such names is that any variable contains data, and any array is a list of something. These names don’t say what kind of data it is or what kind of things are in the list. Essentially, such names aren’t better than x/y/z, foo/bar/baz, New Folder 39, or Untitled 47.
Consider this example:
Besides using Immutable.js and Lodash’s get() method, which already makes the code hard to read, the obj variable makes the code even harder to understand.
All this code does is reorganize the data about the user’s currency into a neat object:
Now, it’s clearer what shape of data we’re building here, and even Immutable.js isn’t so intimidating. I kept the data name because that’s how it’s coming from the backend, and it’s commonly used as a root object for whatever the backend API is returning. As long as we don’t leak it into the app code and only use it during the initial processing of the raw backend data, it’s okay.
Abstract names are also okay for generic utility functions, like array filtering or sorting:
In the code above, arrays and array are totally fine since that’s exactly what they represent: generic arrays. We don’t yet know what values they will contain, and for the context of this function, it doesn’t matter — it can be anything.
Imprecise names don’t describe a value enough to be useful. One of the common cases is names with number suffixes. Usually, this happens for the following reasons:
Multiple objects we have several entities of the same kind.
Data processing we process data in some way and use suffixed names to store the result.
New version we make a new version of an already existing module, function, or component.
In all cases, the solution is to clarify each name.
For multiple objects and data processing, I try to find something that differentiates the values to make the names more precise.
Consider this example:
In the code above, we send a sequence of network requests to test a REST API. However, the names response, response2, and response3 make the code harder to understand, especially when we use the data returned by one request to create the next one. We can make the names more precise:
Now, it’s clear which request data we’re accessing at any given time.
For a new version, I try to rename the old module, function, or component to something like ModuleLegacy instead of naming the new one Module2 or ModuleNew, and keep using the original name for the new implementation.
It’s not always possible, but it makes using the old, deprecated module more awkward than the new, improved one — exactly what we want to achieve. Also, names tend to stick forever, even when the original module is long gone. Names like Module2 or ModuleNew are fine during development, though, when the new module isn’t yet fully functional or well tested.
It’s a good idea to use well-known and widely adopted terms for programming and domain concepts instead of inventing something cute or clever but likely misunderstood. This is especially problematic for non-native English speakers because we usually don’t know many rare and obscure words.
A “great” example of this is the React codebase, where they used “scry” (meaning something like peeping into the future through a crystal ball) instead of “find”.
Using different words for the same concept is confusing. A person reading the code may think that since the words are different, these things aren’t the same and will try to find the difference between the two. It will also make the code less greppable, meaning it will be harder to find all uses of the same thing.
Info We talk more about greppability in the Write greppable code section of the Other techniques chapter.
Tip Having a project dictionary, or even a linter, might be a good idea to avoid using different words for the same things. CSpell allows us to create a project dictionary and ban certain words that shouldn’t be used. I use a similar approach for writing this book: I use the Textlint terminology plugin to make sure I use the terms consistently and spell them correctly in my writing.
Most APIs and programming languages use US English, so it makes a lot of sense to use US English for naming in our project as well. Unless we’re working on a British, Canadian, or Australian project, where the local language may be a better choice.
In any case, consistency is more important than language choice. On several projects, I’ve seen US and UK terms used interchangeably. For example, canceling (US) and cancelling (UK). (Curiously, cancellation is the correct spelling in both.)
Some common words that are spelled differently:
US English
UK English
behavior
behaviour
canceling
cancelling
center
centre
color
colour
customize
customise
favorite
favourite
license
licence
math
maths
optimize
optimise
TipCSpell allows us to choose between US and UK English and will highlight inconsistencies in code and comments, though some words are present in both dictionaries.
Often, we create pairs of variables or functions that do the opposite operations or hold values that are on the opposite ends of the range. For example, startServer/stopServer or minWidth/maxWidth. When we see one, we expect to see the other, and we expect it to have a certain name because it either sounds natural in English (if one happened to be a native speaker) or has been used by generations of programmers before us.
Some of these common pairs are:
Term
Opposite
add
remove
begin
end
create
delete
enable
disable
first
last
get
set
increment
decrement
lock
unlock
minimum
maximum
next
previous
old
new
open
close
read
write
send
receive
show
hide
start
stop
target
source
Tip There’s a certain debate on where to use remove and where delete. I’m not so picky about this and recommend sticking to the add/remove and create/delete pairs where it makes sense. Otherwise, I’m okay with either. The difference isn’t as clear as some like to think: for example, on the Unix command line we remove files using the rm command, but on Windows we delete them using the del command.
Typos in names and comments are very common. They don’t cause bugs most of the time, but could still reduce readability a bit, and code with many “typoses” looks sloppy. Typos also make the code less greppable. So having a spell checker in the code editor is a good idea.
Info We talk more about spell checking in the Spell checking section of the Learn your code editor chapter, and about code greppability in the Write greppable code section of the Other techniques chapter.
Each programming language has its own conventions and idiomatic way of doing certain things, including the way programmers spell the names of variables, functions, and other symbols in the code: naming conventions.
The most popular naming conventions are:
camelCase;
kebab-case.
PascalCase;
snake_case;
SCREAMING_SNAKE_CASE.
Tip There are also lowercase, UPPERCASE, and SpoNGEcAsE, but I wouldn’t recommend them because these conventions make it hard to distinguish separate words.
Most JavaScript and TypeScript style guides suggest the following:
camelCase for variable names and functions;
PascalCase for class names, types, and components;
SCREAMING_SNAKE_CASE for constants.
Tip One of the benefits of naming conventions that use an underscore (_) or nothing to glue words together over conventions that use a dash (-) is that we can select a full name using a double-click, or Alt+Shift+Left, or Alt+Shift+Right hotkeys (these hotkeys expand the selection to the word boundary).
The code that doesn’t follow the established naming conventions for a particular language looks awkward for developers who are used to these conventions. For example, here’s a JavaScript snippet that uses snake_case names:
Note the use of different naming conventions: loud_fruits uses snake_case, and toUpperCase uses camelCase.
Now, compare it with the same code using camelCase:
Since JavaScript’s own methods and browser APIs all use camelCase (for example, forEach(), toUpperCase(), or scrollIntoView()), using camelCase for our own variables and functions feels natural.
However, in Python, where snake_case is common, it looks natural:
One thing that developers often disagree on is how to spell acronyms (for example, HTML) and words with unusual casing (for example, iOS). There are several approaches:
Keep the original spelling: dangerouslySetInnerHTML, WebiOS;
Do something weird: XMLHttpRequest, DatePickerIOS, HTMLHRElement;
Normalize the words: WebIos, XmlHttpRequest, HtmlHrElement.
Unfortunately, the most readable approach, normalization, seems to be the least popular. Since we can’t use spaces in names, it can be hard to separate words: WebiOS could be read as webi os instead of web ios, and it takes extra time to read it correctly. Such names also don’t work well with code spell checkers: they mark webi and htmlhr as incorrect words.
The normalized spelling doesn’t have these issues: dangerouslySetInnerHtml, WebIos, XmlHttpRequest, DatePickerIos, or HtmlHrElement. The word boundaries are clear.
Often, we add intermediate variables to store the result of an operation before passing it somewhere else or returning it from the function. In many cases, this variable is unnecessary.
Consider this example:
And this one:
In both cases, the result and data variables don’t add much to the code. The names don’t adding new information, and the code is short enough to be inlined:
Or for the second example:
Here’s another example that checks whether the browser supports CSS transitions by probing available CSS properties:
In the code above, the alias b replaces a clear name document.body.style with not just an obscure one but misleading: b and styles are unrelated. Inlining makes the code too long because the style values are accessed many times, but having a clearer shortcut would help a lot:
Another case is when we create an object to hold a group of values but never use it as a whole (for example, to pass it to another function), only to access separate properties in it. It makes us waste time inventing a new variable name, and we often end up with something awkward.
For example, we can use such an object to store a function return value:
In the code above, the duration variable is only used as a container for minutes and seconds values. By using destructuring we could skip the intermediate variable:
Now, we can access minutes and seconds directly.
Functions with optional parameters grouped in an object are another common example:
We can use destructuring again to simplify the code:
We removed the options object that was used in almost every line of the function body, making the function shorter and more readable.
Sometimes, intermediate variables can serve as comments, explaining the data they hold that might not otherwise be clear:
Another good reason to use an intermediate variable is to split a long line of code into multiple lines. Consider this example of an SVG image stored as a CSS URL:
Lack of formatting makes it hard to read and modify. Let’s split it into several variables:
While there’s still some line wrapping, it’s now easier to see the separate parts the image is composed of.
We’ve talked about avoiding number suffixes by making names more precise. Now, let’s explore a few other cases of clashing names and how to avoid them.
I often struggle with name clashes for two reasons:
Storing a function’s return value (example: const isCrocodile = isCrocodile()).
Creating a React component to display an object of a certain TypeScript type (example: const User = (props: { user: User }) => null).
Let’s start with function return values. Consider this example:
In the code above, it’s clear which one is the function and which one is the array returned by the function. Now, consider this:
In this case, our naming choices are limited:
isCrocodile is a natural choice but clashes with the function name;
crocodile could be interpreted as a variable holding one element of the crocodiles array.
So, what can we do about it? Not much:
choose a domain-specific name (example: shouldShowGreeting);
inline the function call and avoid a local variable altogether;
choose a more specific name (examples: isFirstItemCrocodile or isGreenCrocodile);
shorten the name if the scope is small (example: isCroc).
Unfortunately, all options are somewhat not ideal:
Inlining can make the code more verbose, especially if the function’s result is used several times or if the function has multiple parameters. It can also affect performance, though it usually doesn’t.
Longer names can also make the code a bit more verbose.
Short names can be confusing.
I usually use domain-specific names or inlining (for very simple calls, used once or twice):
The shouldShowGreeting name describes how the value is used (domain-specific name) — to check whether we need to show a greeting, as opposed to the value itself — whether the user is a crocodile. This has another benefit: if we decide to change the condition, we don’t need to rename the variable.
For example, we could decide to greet crocodiles only in the morning:
The name still makes sense when something like isCroc becomes incorrect.
Unfortunately, I don’t have a good solution for clashing React components and TypeScript types. This usually happens when we create a component to render an object or a certain type:
Though TypeScript allows using a type and a value with the same name in the same scope, it makes the code confusing.
The only solution I see is renaming either the type or the component. I usually try to rename a component, though it requires some creativity to come up with a name that’s not confusing. For example, names like UserComponent or UserView would be confusing because other components don’t have these suffixes, but something like UserProfile may work in this case:
This matters most when either the type or the component is exported and reused in other places. Local names are more forgiving since they are only used in the same file, and the definition is right there.
Names don’t affect the way our code works, but they do affect the way we read it. Misleading or imprecise names can cause misunderstandings and make the code harder to understand and change. They can even cause bugs when we act based on incorrect assumptions caused by bad names.
Additionally, it’s hard to understand what a certain value is when it doesn’t have a name. For example, it could be a mysterious number, an obscure function parameter, or a complex condition. In all these cases, by naming things, we could tremendously improve code readability.
Start thinking about:
Replacing negative booleans with positive ones.
Reducing the scope or the lifespan of variables.
Choosing more specific names for symbols with a larger scope or longer lifespan.
Choosing shorter names for symbols with a small scope and short lifespan.
Replacing magic numbers with meaningfully named constants.
Merging several constants representing a range or a scale into an object or enum.
Using destructuring or inlining to think less about inventing new names.
Choosing domain-specific names for local variables instead of more literal names.