Do you remember watching a professional athlete at an event like the Olympics and telling yourself: “It doesn’t look that hard, I can probably do it with a bit of practice!” followed by you failing miserably at trying to do something even remotely close? When someone is really good at something, they make it look easy. The same effect can be seen when looking at a codebase: good developers not only make the code work, but they also make it look like it was easy to come up with and the logic was simple to implement.
Write to read means we should try to make the code as easy to read as possible for another human being. It’s easy to forget that aspect and focus on making the code readable for a machine since this is the primary goal of what we’re doing when developing a feature. A developer should aim at more than making the code work, though. We often hear about code performance, but there are a lot of other variables that can improve code quality like simplicity, extensibility, and maintainability. Those all become difficult to achieve when the code is not easily readable or understandable.
Readability vs performance
In some situations, performance needs to be at the core of a product. Programs dealing with very high traffic or interacting with critical real-time systems need that focus on performance. On the other side, regular websites and small internal systems with low traffic usually don’t need to be super picky about performance. If you are one of the few who works on a critical system where every millisecond counts, you can obviously disregard this section. Computers are so fast nowadays and can do so many operations per second that the performance boost you get by reducing code size or using tricks to make the code a little bit faster is marginal and won’t make a big difference. For those familiar with the Big O notation, I’m not talking about logic change that would allow you to go from factorial ( O(n!) ) to linear ( O(n) ) since those can have a huge impact on performance and can usually be done without impacting readability. I’m talking about minimal performance improvements like not storing a value in a variable or mutating the reference object so we don’t have to make a new copy. Those micro-optimizations usually don’t matter unless you’re doing thousands of operations per second. By focusing on readability instead of spending time on those optimizations, you can greatly increase the maintainability of the codebase ( and the team output ) while reducing the risk of errors, which will be more beneficial in the long term.
Here are some strategies to improve code readability:
Early returns strategy
Early returns mean that we want to get unwanted situations out of the way first. It will allow us to reduce the number of indentations in the code so we can keep it as flat as possible. It’s making the code easier to read and removes some cognitive load since you don’t have to keep track in which condition context you are when writing or reading the code. It also forces a better control over the function output since you have to be more explicit about the return status for each condition by putting them first.
This is a simple example, but things can really get out of hand fast if there are multiple ifs with some logic in them. Next thing you know, you end up with the Hadouken indentation.
Don’t try to look smart
It’s best to avoid complex one-liners. You are not part of a code-golf.

If you have to concentrate when looking at a line of code, it is usually a bad sign and you should refactor it to keep it simple, stupid. Keeping things simple is something super important for code reviews. The goal is to make it as easy as possible for other developers to review the code. It’s going to make their job easier and the reviews will be better. As an example, try to calculate how much time it takes you to understand what’s happening in the first vs the second examples below:

A good way to look at it is to try to make the code tell a story. Being explicit and making the code as clear as possible should also be part of the team culture. People should not be afraid to say that a part of the code is hard to understand. By doing so, they’re not saying they don’t understand the code or that they’re not good developers. They’re saying that the logic could be written in a way that would make it easier to understand. A team should have a common understanding that code hard to understand is not up to the team standards and should be improved. We should be considerate of other people’s time. While doing code review, developers should spend time reviewing the logic and the architecture instead of trying to understand what the code is doing. It will also help tremendously to the code maintainability since it’s easier to refactor something that is easy to understand and will reduce the risk or errors while refactoring or changing the code behavior. It can also often help understand the “why” as much as the “how”.
Naming is hard
You might have already heard the popular saying
Name your variables as if the person who will next read your code is a psychopath who knows where you live.
or
There are only two hard things in computer programming: cache invalidation and naming things.
Phil Karlton
It’s common to have a hard time naming a variable, at least not for me since I’m awful at it. This is an important step when writing code since a wrong variable name can quickly cause confusion throughout a codebase. Using a generic variable name, like x
, will add a layer of abstraction to the code since people will have to infer what it means from the context. A bad nomenclature can also be misleading, leading team members to understand something different and create confusion throughout the team.
It’s also good to stay boring. Either for a project, module, or variable name. It can be tempting to get creative and use a reference or try to look funny. Usually, if the name is boring, it means it does his job. Turnover in an engineering team can be more frequent than in other industries, so you should never assume that there will have someone there able to explain the context in the future.
It’s also important to give as much context as possible when choosing a variable name. Generic names like x
, data
, params
should be avoided. If by trying to give enough context you end up with a super long name like GetUserDetailAndCreateBookIfMissing
, it usually means the function is doing too many things at once. Splitting the logic into multiple smaller parts can be a solution to those long names. You can also create modules to isolate a part of the code; Book.getByName(...)
instead of getBookByName
.
Size Matters
Sometimes smaller is better! Let’s take a function size as an example. The longer it gets, the more complex it becomes. There’s a principle called single responsibility that really helps to avoid those issues. As much as possible, a function should do only one thing but to do it well. Keeping a function simple will also have the side effect of being easier to unit test and make the code more robust.
The same concept also applies to code reviews. Introducing too many changes at once can have negative effects. The bigger the pull request is, the harder it is to get good and pertinent feedback. It’s not always possible to have a working feature with only a subset of changes. A way to keep the pull request smaller in these scenarios is to use a feature branch, and gradually introduce changes, even if the code merged isn’t 100% functional. This way it’s going to be easier to review.
Pull requests are useful to propose changes and discuss alternative solutions, but it’s also a good medium to share knowledge and context. Asking questions during the code review process should be encouraged for parts that are unclear to someone since it usually means the pull request lack context or the code lacks clarity.
Stay Consistent
Consistency is key when we want to keep a whole codebase readable. Enforcing it manually can be tough, but fortunately, there are plenty of tools that can help. A code linter like eslint and a code formatter like prettier can be used as part of a team arsenal to automate the process so people can focus on more important things and stop arguing about personal preferences like using spaces vs tabs.
It’s also always a good idea to have a proper code-style guideline documented for things that can’t be covered by those tools. That documentation can be useful for new developers joining the team so they can adapt to the team style more quickly.
Types are your friend
If you worked with strongly typed languages before, you probably don’t need to be convinced. There’s a good reason why there’s a team working on Typescript to bring types to JavaScript. Types can give additional context about the input/output of a function, especially when working on code that is unfamiliar. Having the definition of the parameters received in a function makes it easier to understand what’s going on, especially when we receive complex objects or a collection of data. The same concept applies to the function output. In most cases, Typescript can infer the output of a function based on the code itself, but writing down the input and output typing first when creating a new function will define the contract from the start, putting the focus on the logic inside that function. Additionally, types also have the benefit to add living documentation. There is some downside to typing though since it will add some complexity to a codebase and your code will need to be transpiled back to JavaScript.
Adding comments
Good code should be self-documenting. If you have to explain what’s happening, it usually means the code is not clear enough and could be improved. Sometimes, the logic has to be complex by nature, so it’s not always possible to simplify it enough. Adding a small comment in these situations can go a long way.
A better way to look at comments is to use them to give context on things that can’t be inferred by looking at the code. We usually can understand what the code is doing by looking at it, but we can’t always explain the context around it. If you can’t figure out “why” something was done by looking at the code ( could be because there’s a weird behavior from a third-party library, a product decision that doesn’t fit well into the architecture, or the code is depending on a legacy system ), then it’s a good idea to comment it. We don’t know who’s going to look at that code in the future, so it’s good to assume these people will have zero context about it.
Quick tips
- Avoid nested ternary since it’s making it more difficult to see the separation between multiple ternaries.
- Keep
if
logic simple by splitting conditions with properly named variables.
- Avoid unnecessary negation to prevent ending up with a double negation.
- Split complex operations into multiple steps instead of chaining/indenting them. The main benefit is to force having a clear variable name to every step to give more context to the operation. It will also allow the code to breathe more and makes it easier to think about one specific step at a time.
- Add the
is
prefix for boolean variables. Ex:isOpen
instead ofopen
. - Avoid abbreviations for a variable name. Ex:
firstName
instead offname
,password
instead ofpw
. - Don’t try to write ninja code.
Conclusion
Ideally, features would go from a prototype phase ( making it work ) to a productized phase. In a professional context, speed tends to be highly prioritized for external or monetary reasons, often cutting short the feature development at the prototype phase, or in an unfinished productized phase. It can lead to a lack of vision in the long term and can become really problematic as a codebase grows. Writing code, as well as other forms of writing, is hard. It takes time and experience to be able to write clean code, especially in a larger codebase. This responsibility should not land on the developer’s shoulders only, the whole company needs to be on board. This means that communication with other team members is very important and is something that needs practice and should be nurtured.
When writing code, it’s really hard to get everything right the first time, even for experienced developers. Making the code work is only the first step. The next one is to take a step back and focus on making it clean, maintainable, and easy to understand. Having the right mindset and using a few simple tricks like the ones mentioned above can make a big difference. It will benefit you, other team members, and the whole organization in the long run. This is what makes great developers stand out, they make it look easy, they write to read.