Published on 17/08/2021 by

Nevulo profile picture

Avoid indentation hell with early returns

There's a better way to structure your conditional logic

code quality

Fundamentally, programming in any language consists of executing code, and the code that gets executed changes depending on what state of the program the user is in.

For example, if we're making a game and the user has just won, we might want to make the background change to a green color. On the contrary, if the user loses, we'll change the background to a red color. Our program is simple enough in its current state, and we can visualize how it might look:

1function game() {
2 // ... game logic
3 const winner = user.winner;
4 if (winner) {
5 background = "green";
6 } else {
7 background = "red";
8 }

That's simple enough to read.

What if we wanted to make it so when the user wins AND has a score over 900, they see a gold background? And just to spice things up, if a user has won but has a score of less than 300, they'll see a purple background.

1function game() {
2 // ... game logic
3 const winner = user.winner;
4 const score = user.score;
5 if (winner) {
6 if (score > 900) {
7 background = "gold";
8 } else if (score < 300) {
9 background = "purple";
10 } else {
11 background = "green";
12 }
13 } else {
14 background = "red";
15 }

It's not too difficult to represent all of these conditions in code, but you might be able to spot the problem with this code: if your boss wanted you to add more special cases like, for example, showing a rainbow background when someone has won with a score over 1500, or showing a black background if the player died. All of these "branches" quickly grow out of control and it can start getting hard to follow what's happening, as the condition for showing the red background if the user hasn't won is in an else block which is pretty far down. For some, it takes effort to try and read this code to figure out what it does, and you might find yourself jumping up and down the code to fully understand it.

Let's try and take a different approach. Let's flip this on its head. Literally: let's flip the first if statement to check if the user is not a winner first.

1function game() {
2 background = getBackgroundColor(user);
5function getBackgroundColor(user) {
6 const winner = user.winner;
7 const score = user.score;
8 if (!winner) return "red";
9 if (score > 900) return "gold";
10 if (score < 300) return "purple";
11 return "green";

This is the same code as we saw above, including our special conditions to show a purple background only if the user has won the game and has a score of less than 300, and showing a gold background if the user is a winner but only if they have a score of over 900.

By returning as early as possible, it becomes a lot simpler to read this code because we know everything below the if (!winner) ... line won't be executed unless the user is a winner. If the user is not a winner, we return early setting background to red.

This has a few advantages over our code from earlier which used else heavily:

  • removes unnecessary code
  • reduces logical complexity
  • improves readability

Compare this block of code, which includes an if-else pattern to handle logic:

1function game() {
2 // ... game logic
3 const winner = user.winner;
4 const score = user.score;
5 if (winner) {
6 // 1. if "winner" is true...
7 if (score > 900) {
8 // 2. if "winner" is true and score is > 900
9 } else if (score < 300) {
10 // 3. else if "winner" is true and score is < 300
11 } else {
12 // 4. if "winner" is true and score is > 300 and < 900
13 }
14 } else {
15 // 5. if "winner" is false...
16 }

(the numbers in the comments represent the way I personally read this code, from top to bottom)

... with this block of code which uses the early return pattern which is arguably a lot simpler and less lines of code:

1function game() {
2 // ... game logic
3 const winner = user.winner;
4 const score = user.score;
5 if (!winner) return; // if "winner" is false
6 // below will only execute if "winner" is true
7 if (score > 900) return; // if "score" is > 900
8 // below will only execute if "score" is < 900
9 if (score < 300) return; // if "score" is < 300
10 // below will only execute if "score" is > 300
11 // this final statement will only be executed if:
12 // * "winner" is true
13 // * "score" is < 900
14 // * "score" is > 300
15 return;

That being said, it's worth mentioning there's a time and place to use early return pattern, just like there's always time to use if/else. Both work just fine, but at the end of the day if you're not the only person who needs to read your code, it helps to try and make it as understandable as possible. There are certain cases where returning early is better, but there's also times where returning early too often can cause more confusion.

by sums it up fairly well I think: it comes down to common sense and what you're trying to achieve with the function. Too much of anything is never good.



You need to be signed in to comment