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.`;
}
}
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}!`;
}
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)
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.
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 :)
It’s a transitive quality 🥹
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: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.
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.
If you need to do a cleanup after return then it means the function no longer has a single responsibility, I would refactor this.
Nice tip for more cleaner code, I always use this guard clause in my code
It really makes a difference, doesn't it
100%, reducing code complexity, cleaner and readable, and easily understandable code
It doesn't reduce complexity. If you return from a block inside an
if
then everything after it is equivalent to being in theelse
, so it's exactly the same from a code point of view.It is, however, easier to read without the
else
s 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.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.
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).
Cool tip Grant, thank you!
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.
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!
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.
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)
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
}
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()
};
}
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!"
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.