If/else or just if?

twitter logo github logo ・1 min read

Today in a code review we had a statement like this:

Statement A

if (typeof val === 'string') {
    return 'A'
} else if (val === null || val === undefined) {
    return 'B'
} else {
    return val
}

And a suggestion was made to switch to the following:

Statement B

if (typeof val === 'string') return 'A'
if (val === null || val === undefined) return 'B'
return val

I won't tell you where we came out 😜, but which do you feel is a better way?

Was the suggestion to be concise and avoid if/else logic a good one, or was the original way better?

twitter logo DISCUSS (62)
markdown guide
 

I prefer the second syntax because it acts like a "guard" against further execution, so to speak. The first syntax just encourages more nesting and levels of indentations. To me, it is more readable to have a "guard" condition than to have if-else blocks.

 
 

I call it the Short-circuit or (Fast-fail)[en.m.wikipedia.org/wiki/Fail-fast]. It will help you just worry about less things if it's not followed by else

 

I always go with Statement B. This is known as "early returns" and helps with confusing success/error cases. Take for example:

function createPost(post) {
  // Earliest potential error if no post is provided.
  if (!post) return false;

  // Ensure correct data type.
  if (typeof post !== 'object') return false;

  // Ensure required data is provided.
  if (!post.title || !post.body) return false;

  // We're all good at this point. Assume it's safe to create the post!
  const isPostCreated = create(post);

  return isPostCreated;
}

It's nice to know for sure that the end of the function where we're saving the post to the database will not error out due to bad data, since we already verified those cases earlier.

 

I like this style more - it looks clearer to me, and from the point of working through the logic, I like that the returns just end the function.

If a block just set a value and returned it later, there's a chance it could be reassigned in another buggy block, so I think there's less risk in this style too.

 

This looks great to me. I'd be happy even without the comments as it feels pretty self-documenting.

 

I added the comments just for the sake of the example, but they should definitely be removed for a real app.

 

I prefer the first approach, it's ensures all cases are covered, and is more extensible IMO.

I also prefer a single return per function, excluding sometimes null / wrong validations, as this code tends to be easier to read and debug. In your example, I would use the first method, but assign the return value to a variable, and return it after the statements.

This made me thing of this post on hackernews: Please do not attempt to simplify this code

Kubernetes controller source

  1. Every 'if' statement has a matching 'else' (exception: simple error checks for a client API call)
  2. Things that may seem obvious are commented explicitly

We call this style 'space shuttle style'. Space shuttle style is meant to
ensure that every branch and condition is considered and accounted for -
the same way code is written at NASA for applications like the space
shuttle.

 

Can you elaborate on the "cases are covered" point? In my mind both snippets covered all cases, but I'm open to hearing otherwise.

 

In this case, both examples are the exact same of course.

This is somewhat related to a single return statement in a function, but the syntax of an if/else if/else is more explicit than the various conditions are related to each other.

With few if statements each statement is independent from the others (unless you return in the previous cases, like in your example), therefore more than one of them can be triggered.

Overall, while there aren't many arguments for any of these approach in your specific scenario, I feel like it's a better practice to have a single style of conditions for your whole codebase. As the first approach is more flexible, I strongly prefer using it everywhere!

Consistency is definitely most important. I just did a search through an API that I built recently, and I only have 3 else statements which I can probably refactor out. So, I think it's definitely possible to go all-in on early returns.

 

TIL about "space shuttle style", very interesting.

I'm not sure if the Kubernetes controller source is meant to be used as an example though. Otherwise there would be no comment to warn people from simplifying the code.

 

I prefer B, if only because it is a one to one mapping of cases to output - when reading the function it says:

When function is called
If val is a string, then return 'A'
If val is null or undefined, then return 'B'
Return val otherwise

This is a much more human way of thinking about the possibilities. I don't tend to think in terms of if/else conditions in the real world and I don't feel like much business logic works that way either. Option B is also easier to extend - if later you need to add an "if val is a number, return val.toString" or the like, then that is easy and you can add it wherever it makes sense in your code.

Slightly unrelated, but I also really perfer this style to switch statements. They have a place, but most times they should probably be multiple if statements.

 

@wuz Well said, and I totally agree on the switch statements piece. I'm not sure I've ever been satisfied with the look/readability of a switch statement in JS!

 

I prefer the "return early" syntax. It puts a line in the code where if you pass it, you know some assertion holds. The code above doesn't really illustrate that, but for example:

if (flag1) {
  return;
}
// Anything that follows, regardless of if / else nesting
// I know that flag1 is false.

As opposed to:

if (flag1) {
  return;
} else {
  // some stuff
}
// Some more stuff. Is flag1 false?

In the second example, imagine some ugly nesting. For each block of code you need to think "am I in a level where I've tested flag1?"

 

@farble1670 so do you prefer a hybrid of first and second? Something like:

if (typeof val === 'string') {
    return 'A'
}
if (val === null || val === undefined) {
    return 'B'
}
return val
 

I don't prefer one over the other, but what I do prefer is a single return statement, so you can make more assumptions about the code (eg: should get to the end of the function if there's no error)

let result

if (typeof val === 'string') {
    result = 'A'
} else if (val === null || val === undefined) {
    result = 'B'
} else {
    result = val
}

return result
 

I find it more confusing to return a variable in a return unless it is really basic (like changing the message of a JSON response depending the data given).

Early returns make it sure you don't even up with mutating the response somewhere later in the code. It also make more readable (imo) because not everything is nested.

 

Another option (for those who favour expressions over statements) can be built out of JavaScript's ternary operator:

return (typeof val === 'string') ? 'A'
     : (val === null || val === undefined) ? 'B'
     : val

Quite a popular pattern.

 

Ternary operators are really nice for simple and small statements, but I definitely wouldn't recommend them for anything complex. Makes things to hard to read for me. The same could be said for other developers.

 

I prefer Statement A since it is more familiar, more explicit, and groups the possible outcomes. Almost every programmer has seen and used option A. It doesn't require changing the structure to make an update, if something needs to be added before the return statements, it is easy and predictable.

The second statement seems like brevity for the sake of brevity to me. It is shorter, but I'd argue that it is not concise because it does not present the information clearly (at least to me). On Statement B, the formatting would trigger a red flag for me. I'd likely have to take a second or third look just to make sure I know what the intention of the code was. I don't like single line if-statements because all it takes is accidental reformatting without adding braces to cause issues that are easy to miss. Some will argue that their editor does this all for them so maintainability is not an issue, but when working on a team I don't think that is a safe assumption. I'm sure someone could also argue efficiency gains of option B (smaller JS file), but that seems like overkill for minimal gain.

 

I think the second example is muddying the water by combining the pattern (failing early) with a style choice (single-line, braceless ifs).

I prefer Statement B, or would if it was written like this:

if (typeof val === 'string') {
  return 'A';
}

if (val === null || val === undefined) {
  return 'B';
}

return val;

I like this because it reduces nesting and acts as a barrier to people over-complicating a function. I mean that if the function ends up having to re-use a negated form of one of the earlier conditions or have a separate set of nested ifs after the guard conditions, then it's a big smelly smell and should be moved to its own function instead.

Also, if you think about it in terms of human experience, it's how we work. If you're tending a patch of garden, you don't think about what category of flower you're looking at and cross-reference it in a book if it's obviously a weed.

 

If we know the branches will have return, then B, plain if, aka early return is better.

We don't need to keep the context of whose else we are in anymore most of the time.

Even refactoring it is easier, both in terms of just editing the code and the resulting git diff.

However, if we don't have return...

let val
// ...
if (typeof val === 'string') {
    val = 'A'
} else if (val === null || val === undefined) {
    val = 'B'
} else {
    val = /* do something here lol */ val
}
// ...
use(val)

...then there are two options for me.

1. Extract into function

function testVal(val){
  if (typeof val === 'string') return 'A'
  if (val == null) return 'B'
  return val
}
// elsewhere...
let val
// ...
val = testVal(val)
// ...
use(val)

2. Use a conditional expression

let val
// ...
val =   
  typeof val === 'string'
  ? 'A'
  : val == null
    ? 'B'
    : val
// ...
use(val)

It just depends on the complexity, relevance, and size

What's particularly good about both?
They end up only assigning val once, so after some further refactoring we may end up with the superior const binding!

F.A.Q:

Is early return at odds with functional programming?
Should we strive to use conditional expressions at every size?

No. Used correctly, return is just guards in a match expression. You could see it as advanced pattern matching that is missing from JS syntax.
(Early throw, on the other hand, is usually a hack we have to use for lack of static types.)

 

I use both in two different situations. I use the first form at the top of a function when I'm doing assertions about parameters. Think of it as poor man's type checking or contracts in languages that don't have them, e.g.,

def f(a, b):
    if not instanceof(a, int):
        raise ValueError('a must be an int')
    if a < 0:
        raise ValueError('a must be nonnegative')
    ...

I use the second form in the primary body of code unless I am forced to do otherwise. The lazy reason is that I write in both imperative and functional languages, and in functional languages all ifs must have an else statement, so by always having an else, I have one less thing to keep track of between languages.

The more philosophical reason is that using the else block makes it very clear what the branch is in the execution path. The precondition and postcondition for both branches should be the same and the effects required to bridge that gap are all confined to those blocks. I have come to value this kind of locality more and more over the years.

 

Which ever route is the easiest to read(usually it's the second one) is the one I pick. Less code is more.

I've worked with both a lot, and I find the second one is often better for my cognitive load. If you need if/else/elseif perhaps to many things are going on at once.

It depends on what kind of logic is needed, and which one reduces the amount of code and improves the readability.

 

I reckon that it all depends on what is your target.

Return statements, of course, you can easily use approach 2 or ternary operation. However, if you do any operations on a particular block, rather than return, if-else if -else construction guarantees that if an expression is evaluated to true, other blocks would not be executed, hence -> save your precious runtime. Thus, it would be only visible on a high-demanding calculation blocks.

 

definitely the just "if" boat here as much as possible, although I avoid single line and practice the bracket method.

 

@dechamp something like:

if (typeof val === 'string') {
    return 'A'
}
if (val === null || val === undefined) {
    return 'B'
}
return val

?

 

yup! that is the good stuff right there!

And based off your example I would do this...

check for no value first, save the code from type checking if it's not set.

if (!val) {
   return 'B';
}

if (typeof val === 'string') {
   return 'A';
}

return val;

You have a good point, but are you expecting your system to act like that? It's very rare you need to actually be checking for that, but if you do suspect it then definitely go for the finite checking.

But then again, I love using the "joi" package for my type checking.

Normally no, but in this case yes. It's a bare-bones project and the code is constructing a DB query from a set of known inputs, so null and undefined get treated as null, strings get quotes around them, and everything else is passed through.

 

First one. Brackets are consistent I feel and easier to assess. I've heard the argument about one return per function although then you're potentially creating a local variable to manage state. What's truly important is that your developers can effectively read it, after all the computer obviously doesn't care.

 

Wouldn't you put the null and undefined check first to have a quick return?

if (!val) return B;
return (typeof val === 'string') 
    ? A
    : val;

Of course, you could go back to the more explicit null and undefined check if you think you might get other falsy values that don't fall into that condition like 0, empty string, false, and NaN.

But to answer the question, I prefer just 'if' whenever possible.

 

As long as every branch returns then the 2nd version is fine with me.
(Although I'd still use brackets instead of guard clauses because of personal preference regarding readability)

It becomes a problem when the if does not return and gets more complex than a simple one liner.
I.e.

if (env.DEBUG) {
    Logger.log(/* message */);
}
// Rest of logic here

would obviously be okay, but


let var1;
let var2;
let var3;

if (/* Some condition */) {
    // Many lines
    // of code
    // that affect 
    // the state of 
    // the variables
}

// Rest of logic
// working on the 
// variables unconditionally,
// here

return result;

would not be okay, because it makes it really hard to reason about the output of the function and the logical flow of your data can't be parsed at a glance.

 

The second one. Every time. For the early returns reason mentioned earlier. Since you're doing a return, the else is an anti-pattern.

Also this thread terrifies me for all the code that exists out there and solidifies everything about xkcd.com/2030/

 

Hello, I prefer the second one, because in the past when I was reviewing my peer's code I was faced with the first style a lot and it put toll in my mind to process the meaning.

But I used to like the first one because it puts my mind at ease when writing it, knowing the else part is covered explicitly. But some time after when I need to read it again, to be honest, its not a nice experience.

 

In favor of not having a if/else block if I can avoid it.

return typeof val === 'string' && 'A' ||
     val === null || val === undefined && 'B' || val;

or to be a little more descriptive

const isString = typeof val === 'string';
const isEmpty = val === null || val === undefined;

return isString && 'A' || isEmpty && 'B' || val;
 

This is a classic versus modern debate.
Classics are classics because they stood the test of time, in which case statement A is preferred.
If any calculation were needed after the if and before the return, you would need Statement A in any ways, so isn't it more consistent?

I'm totally over trying to get code into super compact pieces after seeing a 140 line program shoved into a one liner in a coding competition :D Took me 140 hours to figure out what it does!

 

It depends on the case, but I would probably pick a mixed case like this:

if (typeof val === 'string') {
return 'A';
}
if (!val) {
return 'B';
}
return val;

The main reason is because blocks are in my opinion better to see and less confusing. I also try to avoid the tenary operator, but it really depends on the case. Sometimes it's just perfect. There is no need to write an else in this case. The else if wouldn't improve any performance, and for me it's better to have no else case at all.

 

I'd definitely go for early returns.
less indentation, the logical steps are easier to follow, less code to read when you navigate through breakpoints while debugging.
also easier to further refactor if the single if statements get too long or complicated -> just move them to another function, which by the way would be unit-testable too.

 

If present then Stick to codebase convention
Else prefer readbility of the method

😬

 

Well which one is more "readable"? 🤔😎

 

As our phylosophy teacher used to say: we are debating about angels gender... Anyway I think I stick more for the first one. I work in Java and there is a plenty of other situation to use one-liners.

 

I talked about that in one of my articles, using Python instead.

What I found out is that there's not just one good answer for these types of questions.

Feel free to read the comments there to, I found all of them pretty interesting, and they can apply to JavaScript to :)

 

I think the original way is clear and more readable.

 

At the beginning of my career I used to use the if-else syntax. Now I see just if syntax more readable

 

I always try to save as much space as I can, so I guess it's the choice B for me.

spaceSaving ? if : if/else;
 
 

I prefer the second approach, but with curly braces.

Generally speaking, I return ASAP like you did to avoid nested code and big if/else statements.

 

I prefer just if with early returns as it reduces nesting conditions.

 

My vote goes for option B.
Since I learned the Swift guards I prefer that style.

 

I'd keep the curly braces but remove the last "else" because it's useless.

 

I prefer to use Statement B. Statement A just happens to be concise, and its simplicity will lost only by a slight change.

 

I'd go w/ B, but I'd break the line. I'm not a fan of single line ifs

 

The second approach is nice if and only if all of your branches result in a return; however, it's not great if you're trying to do something else with the result of evaluating the conditions.

 
 

don't forget the switch! :) quite often it's a lot cleaner solution than multiple if/else statements

 
 

I'd use the second for simple conditions and as 'guards', mentioned below. The more verbose if/else if/else would be used for more verbose conditions.

 
Classic DEV Post from Jan 16

What would you like to see on your DEV profile?

If we were to rethink the DEV profile, what would you like to see on it?

Tyler Warnock profile image
Love technology and interested in good. Mostly JS right now.