Prettier, the most popular JavaScript code formatter, recently released a novel way to format nested ternaries under an experimental flag. This has come after years of disagreement over the best and most readable way to format a nested ternary.
I have a better idea of how to make nested ternaries clearer: stop nesting them.
What do you mean by nested ternary?
The ternary operator is an alternative to if
/else
for making decisions based on a condition. A regular ternary expression looks like:
A nested ternary is where you enter further ternary expressions in either the true or false branch.
Let's take a look at why this isn't good practice.
Why nested ternaries are bad
I appreciate Prettier and all it has done to help us write consistently-formatted code. The project is opinionated, which prevents bike-shedding and lets us get on with the more important work of building projects. It promotes consistency, one of the four properties of Clean Code. The least you can do if you are going to nest ternaries is to format them in a consistent manner.
I love that the Prettier project is working on making this better for all code, including nested ternaries. But looking over the hundreds of comments on how Prettier should format nested ternaries, I can't help but think that there will never be consensus on this topic.
Furthermore, another property of Clean Code is intentionality; code should be clear and straightforward. Nested ternaries are rarely clear or straightforward; I personally find them much harder to read and understand than other forms of conditionals.
We read code many more times than we write it, so it should be as easy to parse in one's head as possible. Picking your way through question marks and colons to determine what an expression means is much less clear than reading if
/else
statements that spell it out for you.
It's not just me who believes this; Sonar does not believe that nested ternaries are clear and straightforward, either. SonarQube Server, SonarQube Cloud and SonarQube for IDE all enforce the rule that ternary operators should not be nested as part of the Sonar way profile.
Clearer ways
So, what do we replace a nested ternary with? Let's take the example nested ternary from Prettier's example and rewrite it a few different ways. Their example code, in their new format, is:
Nested conditionals
The easiest way to replace a nested ternary is by turning them into a conditional
In this example, we set up a variable that we will assign within the conditional statements. We can start the variable off with its default value, saving one else
clause. Then, we apply the same logic as in the original example but with if
/else
blocks.
One of the issues that some developers have with this style is the use of the let
variable. The ternary operator does have the benefit that it is an expression; that is, it returns a value. Meanwhile, if
/else
statements do not return anything, you need to either mutate a variable or use return
.
If using a variable doesn't feel right, you can refactor the statement into its own function and use early returns.
When you refactor the behaviour into its own function, you can independently test the functionality, ensuring that it is correct and cannot be broken unintentionally. And you can drop the extra variable assignment.
Reduce the nesting
The real issue in the code lies in the nesting itself. Nesting is one feature that makes code more complex.
In this example, we can refactor the nesting out to make this function easier to understand:
Now, the function is clearer and uses fewer lines than the nested ternary we started with.
I like that this is a separate function that you can write tests for. But if it is important to you to include this behaviour in its original location, you can do so as an IIFE (instantly invoked function expression):
Personally, I like extracting the code to a separate function for the reasons I described above, plus I think the IIFE adds extra clutter. You may disagree, in which case the IIFE is an option.
In praise of nested ternaries?
Eric Elliott wrote in favour of nested ternaries, calling them "chained ternaries" once you perform operations on the conditions to ensure you only ever chain in the else
clause of the ternary. His points about the difference between statements and expressions are good, and we should avoid side effects and mutation where we can, and expressions allow for that.
His first argument is that if
/else
statements permit you to write code that causes side effects. However, once you have reduced a conditional to code that you can write as a nested ternary, you can also avoid side effects and mutation by rewriting it as a function with returns, as we did above. On the other hand, you can absolutely write mutations and side effects within a ternary, so it seems to be a moot point to me.
His second argument is that the syntax of an if
/else
statement is clutter that takes up working memory, causes interference and leaves a greater surface area for bugs. For me, parsing a ternary requires me to come up with the syntax in my head, so even if the file doesn't say "if" and "else", my understanding of a ternary requires that. The idea that changing if
s and else
s to question marks and colons gives you less surface area for bugs is a bit far-fetched for me. I'm not going to argue that using if
/else
statements will help you write more correct code and I discount that using fewer characters will achieve that either. I contend that if
/else
statements make the code clearer and easier to understand. And, for me personally, the effort of understanding and translating the ternary syntax actually increases my likelihood of introducing bugs during maintenance.
Is there any place for nested ternaries?
Those of you using JSX might well be fuming by this point.
There are no if
/else
statements in JSX, so you need to use the ternary operator to make decisions. It is then common to nest those operations when rendering components conditionally and including conditional attributes in code like this:
Of course, nested ternaries are necessary in JSX, so the above code is perfectly reasonable. My recommendation is still to minimise the nesting as best as you can. It's also worth noting that the Sonar rule for nested ternaries does not apply to JSX.
Minimise nesting and prioritise clarity over brevity
Nesting in code introduces complexity, so the first thing you should do when you find yourself with a nested ternary is to try to refactor to reduce the nesting as much as possible. Then, you can use any of the patterns above to remove the nested ternaries.
The one thing you can say about any of the alternatives above is that they all use more characters and involve more typing than the ternary-based solution. However, as I asserted earlier, code is read much more often than written.
The Prettier blog post describes if
/else
statements as "ugly", but I will always prefer ugly, understandable code over picking apart the question marks and colons in a nested ternary.
When you are intentional with your code and choose to minimise nesting and use fewer ternary operators, you will find your code is clearer and easier to understand and change over time.
If you'd like a tool in your IDE to remind you of this as you type, install SonarQube for IDE for free. And if you're not already formatting your code, add Prettier to your project, too. Because if you do have nested ternaries, formatting them might improve the clarity of your code until you get around to refactoring them.