DEV Community

Cover image for Don’t ever use if-else. Use this instead
Tymek Zapała
Tymek Zapała

Posted on • Edited on

Don’t ever use if-else. Use this instead

Let’s talk about a coding trick that makes your code easier to read and keeps things organized: early returns.

Lots of coders rely on if-else statements to check different conditions, but stacking them up can get messy. Instead, early returns let us handle all the error cases up front, so we can save the ideal scenario for the end of the function.

Example

Here’s a function that checks if a user can get a discount:

function getDiscountMessage(user) {
  if (user.isActive) {
    if (user.hasDiscount) {
      return `Discount applied for ${user.name}!`;
    } else {
      return `${user.name} does not qualify for a discount.`;
    }
  } else {
    return `User ${user.name} is inactive.`;
  }
}
Enter fullscreen mode Exit fullscreen mode

This code is filled with nested if-else statements. 🤮

Instead, we can cover the error cases first with early returns and then focus on the "perfect scenario" at the end:

function getDiscountMessage(user) {
  if (!user.isActive) {
    return `User ${user.name} is inactive.`;
  }

  if (!user.hasDiscount) {
    return `${user.name} does not qualify for a discount.`;
  }

  // Perfect scenario: user is active and qualifies for a discount
  return `Discount applied for ${user.name}!`;
}
Enter fullscreen mode Exit fullscreen mode

Each error condition is handled in a single line right at the start. This keeps our code neat and straightforward, without all the if-else blocks of different nesting levels to follow. Our pattern is also very helpful for keeping one level abstraction per function, which I covered in another article.

So next time, skip the if-else and give early returns a try. 😎

Top comments (29)

Collapse
 
morewings profile image
Dima Vyshniakov • Edited

Early returns are useful. But be careful. Now your logic critically depends on the order of if statements and existence of each of them (basically you have to rewrite blocks 3,4,5… if you remove block 2). Sometimes this approach can be hard to refactor.

More reliable way of doing this is to use some kind of mapping.

Collapse
 
tymzap profile image
Tymek Zapała

Totally agree, mapping is great if you have lots of cases and don't want to be dependent on order.

However, early return is alternative to if-else, not the mappings :)

Collapse
 
morewings profile image
Dima Vyshniakov

It’s a transitive quality 🥹

Collapse
 
rdentato profile image
Remo Dentato • Edited

This is fine for just returning but it's not always ideal.

If you have common tasks that need to be completed at the end of the if/else chain you need to be careful:

  if (test_a)  do_someting_a(); 
  else if (test_b) do_something_b();
  else do_something_c();

  long_cleanup();
  return;
Enter fullscreen mode Exit fullscreen mode

You can re-factor the end task in a function and call it in every branch. Except that if have any local variables you need to use for your cleanup, you have to pass it to the function.

Or you can write some "partial cleanup" code in every branch. There are options but you have to weight them.

In the end, use these "early returns" whenever they make the logic simpler to follow but do not forget that the key principle of structured programming ("one entry and one exit for any code block") is there for a reason, it keeps the entire code easier to maintain and modify.

Collapse
 
tymzap profile image
Tymek Zapała

If you need to do a cleanup after return then it means the function no longer has a single responsibility, I would refactor this.

Collapse
 
charles_roth_8c0df94d211a profile image
Charles Roth

Yes... except "one entry and one exit for any code block" which is a largely a myth. It's useful for languages like C++ where you have to do cleanup (e.g. if you allocated some memory) at the end. But for more modern languages (Java, Python, etc.) that have automatic garbage collection, the "one return" has no particular virtue.

A series of guard clauses, each with its own return, is much easier to read and understand, compared to a single return for any entire method or function.

Collapse
 
grantdotdev profile image
Grant Riordan

Good tip for beginners. 🙌 if you’re looking for the term to explain what you’re doing here. It’s called a “Guard clause” as guards your code and short circuits it (returning early).

Collapse
 
tymzap profile image
Tymek Zapała

Cool tip Grant, thank you!

Collapse
 
wizard798 profile image
Wizard

Nice tip for more cleaner code, I always use this guard clause in my code

Collapse
 
tymzap profile image
Tymek Zapała

It really makes a difference, doesn't it

Collapse
 
wizard798 profile image
Wizard

100%, reducing code complexity, cleaner and readable, and easily understandable code

Thread Thread
 
moopet profile image
Ben Sinclair

It doesn't reduce complexity. If you return from a block inside an if then everything after it is equivalent to being in the else, so it's exactly the same from a code point of view.

It is, however, easier to read without the elses and if you keep your functions short and don't need to do anything with the values you've just set after the condition, then it's the way to go.

Thread Thread
 
ilittlewood profile image
Ian Littlewood

The final generated code may be very similar, even identical, but when it comes to READING that code, reducing nesting most definitely DOES reduce complexity because it requires holding only a single state in mind. With nested conditions, it's necessary to hold all passed conditions up to that point.

Collapse
 
wafa_bergaoui profile image
Wafa Bergaoui

Stacking if-else statements can quickly become confusing, especially in larger functions. This approach not only makes the code cleaner but also easier to maintain. Thanks for sharing this helpful tip!

Collapse
 
webbureaucrat profile image
webbureaucrat

This is a good way to really maximize how imperative your code is. I much prefer expressive style.

If you don't like the nesting, you can always break it out into multiple functions.

function getDiscountMessage(user) {
    return user.isActive ? getDiscountMessageForActive(user) : `User ${user.name} is inactive.`;
}

function getDiscountMessageForActive(user) {
    return user.hasDiscount ? `Discount applied for ${user.name}!` : `${user.name} does not qualify for a discount.`;
}
Enter fullscreen mode Exit fullscreen mode

The benefit here is you are syntactically guaranteed to have considered all cases, whereas imperative if without an else makes the else implicit instead of explicit and sets you up for mistakes if you or another maintainer need to change the code in the future.

(inb4: of course this is a contrived example and IRL I'd probably just have active and inactive users be completely different classes or something to avoid boolean flag code smell)

Collapse
 
qwetyuiop profile image
Rey

title nowadays : 1 being creative the others duplicate. I'm sick of seeing any content started with "Don't..." "Stop..." they keep coming to my email like sent by the wisest man in the earth.
vuck yue.

Collapse
 
pathus90 profile image
Diallo Mamadou Pathé

Hello,
It can be break like this using strategy design pattern too.

// Step 1: Define the Strategy Interface
class DiscountStrategy {
getDiscountMessage(user) {
throw new Error("This method should be overridden by subclasses.");
}
}

// Step 2: Implement Concrete Strategies
class ActiveWithDiscountStrategy extends DiscountStrategy {
getDiscountMessage(user) {
return Discount applied for ${user.name}!;
}
}

class ActiveWithoutDiscountStrategy extends DiscountStrategy {
getDiscountMessage(user) {
return ${user.name} does not qualify for a discount.;
}
}

class InactiveUserStrategy extends DiscountStrategy {
getDiscountMessage(user) {
return User ${user.name} is inactive.;
}
}

// Step 3: Implement the Context Class with a Lookup Table
class DiscountMessageContext {
constructor(user) {
this.user = user;
this.strategy = this.selectStrategy();
}

// Lookup table to map conditions to strategies
selectStrategy() {
const strategies = {
'activeWithDiscount': new ActiveWithDiscountStrategy(),
'activeWithoutDiscount': new ActiveWithoutDiscountStrategy(),
'inactive': new InactiveUserStrategy()
};

// Determine the key based on the user's properties
const key = this.user.isActive 
  ? (this.user.hasDiscount ? 'activeWithDiscount' : 'activeWithoutDiscount')
  : 'inactive';

return strategies[key];
Enter fullscreen mode Exit fullscreen mode

}

getDiscountMessage() {
return this.strategy.getDiscountMessage(this.user);
}
}

// Usage
const user = { name: "Alice", isActive: true, hasDiscount: true };
const discountContext = new DiscountMessageContext(user);
console.log(discountContext.getDiscountMessage()); // Output: "Discount applied for Alice!"

Collapse
 
amitrastogi2202 profile image
Amit Rastogi

How about when the perfect scenario logic also depends on certain conditions like -

//processing perfect scenario logic
if (condition 1) {
//process differently
}
else if(condition 2) {
//process differently
}
else {
//process differently here again
}

Collapse
 
appurist profile image
Paul / Appurist

This is common practice for me (and I've been coding professionally for over 40 years now). However, I will take exception to your title, which argues against a fundamental feature of most languages. I thought you were going to argue for ternary operators everywhere. ;)

Instead, I think what you're saying is "replace if-else with if-return where possible" to clean up code. I'm definitely with you on that.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.