DEV Community

Cover image for If-Else or Switch-Case: Which One to Pick?

If-Else or Switch-Case: Which One to Pick?

Sumudu Siriwardana on November 10, 2021

This article was originally published on Dasha In case you are wondering, Dasha is a conversational-AI-as-a-service platform that lets you embed ...
Collapse
jonrandy profile image
Jon Randy • Edited on

You can also use switch like this:

switch(true) {
  case x==1:
    // code block
    break;
  case y==3:
    // code block
    break;
  case z / 5 >= 3:
    // code block
    break;
  default:
    // code block
} 
Enter fullscreen mode Exit fullscreen mode
Collapse
sumusiriwardana profile image
Sumudu Siriwardana Author

Yes, thank you for mentioning that. But I've heard that having "true" is not the best practice of writing it. But I definitely have to do more research on this and understand why.

Collapse
darkwiiplayer profile image
DarkWiiPlayer • Edited on

Because jump-tables go out the window when your cases aren't constants. Other languages (Looking at you, C) don't even allow this in the first place.

If you ask me, this is not a good reason to avoid using this feature; but one should be aware that it won't be optimised the way you could with constant expressions*.

* Although, a really smart JIT-Compiler might figure out that an expression is constant for a specific closure (or trace, if it's a tracing JIT) and optimise it into a jump table. Don't think any JIT would bother with such a specific optimisation, but it is an option.

Thread Thread
pinotattari profile image
Riccardo Bernardini • Edited on

A good reason to avoid this construct is readability: the typical "switch" construction is turned upside-down with a constant as "controlling value" and non-constant cases. ...and this flip gets you... nothing?

It is interesting as study case, as "look what you can do" but I cannot see a reason (read: a real advantage) to use it in a real case and the disorientation that causes to the reader is a big disadvantage.

Collapse
jonrandy profile image
Jon Randy • Edited on

Don't see why not. It can make some convoluted blocks of ifs easier to read if you ask me. Nothing odd going on - switch just compares the value of one expression with the values of the expressions listed as the cases. We can match any expressions that are true.

It's probably not considered 'best' practice as it doesn't adhere to the irrational dogma of the high priests of 'clean' code

Thread Thread
murkrage profile image
Mike Ekkel

There's literally no need for the last sentence of your comment.

As to why it's considered not a best practice: while you could definitely use a screwdriver as a hammer, that's not the intended use. It's an incredibly smart way of using the switch statement but it's not the way it's intended to be used and as such is harder for people to understand.

Thread Thread
jonrandy profile image
Jon Randy • Edited on

If we continually write code catering to the lowest common denominator of understanding, developers will never learn all about how the languages really work - denying them the chance to improve their knowledge.

I'm a firm believer in increasing real understanding of languages. We should never tell developers they 'must' do this, that, or the other - rather, we should let them learn real understanding of how stuff works... and then - armed with that knowledge they can decide how best they can utilise the language.

Showing them something that challenges their understanding of a language feature can only benefit them, by gaining a fuller understanding

Thread Thread
murkrage profile image
Mike Ekkel

This isn't about catering to the lowest common denominator, though. This is about writing clever code that has no reason being clever. It doesn't add to a person's understanding of how the language works because, like I said, it's telling someone to use a screwdriver as a hammer. The entire premise of the switch statement is to compare the expression to the cases.

Your code works, I'm not arguing that, just like you could easily hammer away at a nail using a screw driver but at what cost?

Thread Thread
jonrandy profile image
Jon Randy • Edited on

The entire premise of the switch statement is to compare the expression to the cases.

Which is precisely what my example is doing. Yes, it looks different to most examples of switch statements, but it's really no different - just comparing expression with expression

Thread Thread
shuckster profile image
Conan

Gentleman gentleman, there is a third way.

(I hope you don't find the self-promoted library distasteful - I link to others at the bottom of this very short article, and of course reference the TC39 spec for pattern-matching.)

By the way, I've found that even though switch (true) { ... } not so common, the reaction I usually see from those learning it for the first time is "Oh wow, you can do that?". There's just not a lot of friction to understanding it.

Thread Thread
xtofl profile image
xtofl • Edited on

The entire premise of the switch statement is to compare the expression to the cases.

Which is precisely what my example is doing.

... for certain interpretations of 'precisely': switch(true) { case f(x): ...} is effectively, undeniably comparing true to a number of predicates.

It is effectively, undeniably not comparing a value unknown at design time to one of several, distinct, possibilities, all known at design time.

Proper understanding of the language would lead to the realization that this is the opposite of what switch is intended for.

When any developer, whatever their programming language, sees a switch, they will rightfully expect an enumeration of non-overlapping cases. This assumption allows them to effectively communicate intent.

Their surprise will lead to misunderstandings, lost time, bugs, everything the one paying for the code does not want.

We should distinguish 'understanding of languages' and 'understanding what the runtime makes of it'. If we mix these up, the latter interpretation would give us carte blanche to use whatever makes the program work and blame our colleagues for not 'understanding the inner workings'. I happily refer to Duff's device and isqrt for some examples of where this leads to; justified in some cases, but to be shunned in general.

switch(true) does not belong in production code. I will refuse to merge a pull request containing it.

Of course, every language needs poetry. So allow me to step out of my dogmatic production-code mindset, and appreciate the reversal of the intended use as an form of art.

Thread Thread
shuckster profile image
Conan

When any developer, whatever their programming language, sees a switch, they will rightfully expect an enumeration of non-overlapping cases. This assumption allows them to effectively communicate intent.

I'm not convinced that intent is so clearly carried by syntax and convention alone. Surrounding context counts for a lot, and can make or break the understanding of any chunk of isolated code. I think it's perfectly reasonable to permit out-of-usual syntax so long as you can see what's going on when zooming out a bit.

I'm also not too sure that the two C/C++ examples you gave are germane. JS doesn't have proper pointers, let alone the ability to abuse them for type-casting. It can't have interleaved syntax either. switch (true), fall-throughs, and omitting default: are all naturally permitted by the spec.

If you don't like them, drop an eslint rule and they won't even make it into a PR. Incidentally, I wonder if a marvel like InvSqrt would even exist if we had linters back then? 🤔

Thread Thread
jonrandy profile image
Jon Randy • Edited on

Yup, I'm a hoot in code-reviews... luckily, I'm normally leading them. Just call me Leonardo of the PR

Thread Thread
xtofl profile image
xtofl

Totally agree: syntax alone won't convey all your intent. Like a calligrapher can still write absolute nonsense. The point was that switch(true)... actively hides the intent.

Thread Thread
xtofl profile image
xtofl

Now that, I can appreciate: the great artist-inventor's role is to experiment with the know rules (hey someone has to do it!) and build interesting and beautiful devices, some adopted right away, others understood only after 400 years :).

Collapse
qwertydude profile image
Chris Howard

Big fan of this method. I know it's an anathema to the purists... but if my language allows it, then they don't have a problem, so I'll use it.

Collapse
lukeshiru profile image
Luke Shiru

One thing I do quite often is when I have a bunch of values that will return other vales, it feels like a "map", so instead of something like this:

function studentFinalResultIf(grade) {
    if (grade === "A+") {
        return "Nailed It! 🥳";
    } else if (grade === "A") {
        return "Passed 💃";
    } else if (grade === "B+") {
        return "Passed 💃";
    } else if (grade === "B") {
        return "Passed 💃";
    } else if (grade === "C") {
        return "Barely Survived 😌";
    } else if (grade === "D") {
        return "Failed 😢";
    } else {
        return "Failed 😢";
    }
}
Enter fullscreen mode Exit fullscreen mode

I have stuff like this:

const studentFinalResultIf = grade =>
    ({
        "A+": "Nailed It! 🥳",
        A: "Passed 💃",
        "B+": "Passed 💃",
        B: "Passed 💃",
        C: "Barely Survived 😌",
        D: "Failed 😢",
    }[grade] ?? "Failed 😢");
Enter fullscreen mode Exit fullscreen mode

And for the if example, similarly if I only return one thing or other based on a given value, I just user a ternary:

const studentGrade = marks =>
    marks >= 50 ? "You have passed the exam! 🥳" : "You have failed the exam!";
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
darkwiiplayer profile image
DarkWiiPlayer

In the if-else case, we do not create a jump table, and all cases are executed at runtime.

This is not necessarily true; compilers may still optimise if-statements into jump-tables if they're simple enough. Whether any compiler bothers doing this is another question, of course.


Also, another option is to build your own jump-tables. In JavaScript, these are called "objects" 😜

Collapse
foxy4096 profile image
Aditya Priyadarshi

wait what is === and == and = in js
three equal to sign is very confusing

Collapse
jonrandy profile image
Jon Randy

= is assignment
== is equality (no type checking)
=== is strict equality (type checking)

My explanation here is greatly simplified - the rules for equality when using == are quite complex

Collapse
pinotattari profile image
Riccardo Bernardini

the rules for equality when using == are quite complex

Alas, that is one of the worst sin of JavaScript. == is not even transitive, which is a basic property that you would expect from equality. It is not just an academic matter, some algorithms (e.g., sorting) make the implicit hypothesis that equality is transitive and they can break down if it is not. The reason why we do not observe many break downs is that in sorting procedures data are of the same type and this "feature" of JS == (but also PHP has the same problem) does not manifest itself.

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
pinotattari profile image
Riccardo Bernardini

I do not have the possibility of checking it, but if I remember correctly

"1.200" == "1.2"
Enter fullscreen mode Exit fullscreen mode

is true because since both sides "look" like floating point numbers, they are converted both to float before doing the comparison.

Thread Thread
peerreynders profile image
peerreynders • Edited on
console.log('1.200' == '1.2'); // false
console.log(1.2 == '1.2'); // true
console.log('1.200' == 1.2); // true
Enter fullscreen mode Exit fullscreen mode

MDN: Equality (==) — Comparison with type conversion

  • If the operands are of different types, try to convert them to the same type before comparing:
    • When comparing a number to a string, try to convert the string to a numeric value.
    • If one of the operands is a boolean, convert the boolean operand to 1 if it is true and +0 if it is false.
    • If one of the operands is an object and the other is a number or a string, try to convert the object to a primitive using the object's valueOf() and toString() methods.
Collapse
sumusiriwardana profile image
Sumudu Siriwardana Author

Jon has explained it very simply!

It might help you to get a basic idea of JS operator with this article
dev.to/sumusiriwardana/beginners-g...

Collapse
peerreynders profile image
peerreynders • Edited on

When in doubt — use functions.

const topResult = (next) => (grade) =>
  grade === 'A+' ? 'Nailed It! 🥳' : next(grade);

const midGrades = new Set(['A', 'B+', 'B']);
const midResult = (next) => (grade) =>
  midGrades.has(grade) ? 'Passed 💃' : next(grade);

const lowResult = (next) => (grade) =>
  grade === 'C' ? 'Barely Survived 😌' : next(grade);

const failResult = (_grade) => 'Failed 😢';

const studentFinalResult = topResult(midResult(lowResult(failResult)));

console.log(studentFinalResult('A+')); // "Nailed It! 🥳
Enter fullscreen mode Exit fullscreen mode

😁

MDN: Closures

Collapse
taniarascia profile image
Tania Rascia

This is uniquely terrible. It's so much less readable than either an if/else or a switch.

Collapse
peerreynders profile image
peerreynders

This is uniquely terrible.

Chain of Responsibility sends its regards.

Collapse
pinotattari profile image
Riccardo Bernardini

A side note from a different language...

In Ada you can use switch .. case (actually it is case .. when like in Ruby) only with discrete types: integers and enumerations. The nice part of the case in Ada is that you are obligated to specify all the cases. It can seem like a burden, but it saved me from many bugs that would had happened because I forgot to update a case after adding a new item to an enumeration type.

Of course there is the "default" case (when other), but I try to avoid it since it "disables" the safety check that all the cases are specified.

What about when you do a case with an integer? You cannot, of course, specify all the integers... Well, in that case you can use the when other (it is legit, after all), but often is better to define a new integer type with explicit bounds.

Collapse
peerreynders profile image
peerreynders

TypeScript supports exhaustiveness checking on union types.

type Grade = 'A+' | 'A' | 'B+' | 'B' | 'C' | 'D' | 'F';

function studentFinalResult(grade: Grade): string {
  switch (grade) {
    case 'A+':
      return 'Nailed It! 🥳';
    case 'A':
    case 'B+':
    case 'B':
      return 'Passed 💃';
    case 'C':
      return 'Barely Survived 😌';
    case 'D':
    case 'F':
      return 'Failed 😢';
    default:
      const _exhaustiveCheck: never = grade; // "Type 'string' is not assignable to type 'never'".
      return _exhaustiveCheck;               // if one of the union type values is missing
  }
}

console.log(studentFinalResult('A+')); // "Nailed It! 🥳"
Enter fullscreen mode Exit fullscreen mode

playground

Collapse
taniarascia profile image
Tania Rascia

If you can turn an if/else into a switch, it's probably better as an object.

const NAILED_IT = "Nailed It! :partying_face:";
const PASSED = "Passed :dancer:";
const BARELY_SURVIVED = "Barely Survived :relieved:";
const FAILED = "Failed :cry:";

const gradeMap = {
  'A+': NAILED_IT,
  'A': PASSED,
  'B+': PASSED,
  'B': PASSED,
  'C': BARELY_SURVIVED,
  'D': FAILED,
  'F': FAILED
}

return gradeMap[grade]
Enter fullscreen mode Exit fullscreen mode
Collapse
aerodarius profile image
Dario Cruz

"maps" ofcourse, neither "if-else", nor "switch"