14 ms·
Using the switch(true) pattern in JavaScript
- jimjimjimjim 6y agoI think the beauty of the switch statement is that when you see one you know you're just concentrating on the value of one variable. I think the if else if is actually cleaner for the user example in the post.
- elyseum 6y agoA good example of: just because you can, it doesn’t mean you should. Yes, the if/else sequence is a bit more unstructured in terms of code layout, but chances the next developer browsing the code will instantly know what it does will be much higher.
- csmattryder 6y agoSpare a thought for the junior/mid-level devs who don't understand clever patterns, and the seniors who haven't read the same blog posts as the implementor of the switch(true). Completely agree, it's like writing readable prose, understand your audience. That said, give me proper pattern matching in JS. Hard to live with languages that haven't caught up yet.
- nicoburns 6y ago> That said, give me proper pattern matching in JS. Hard to live with languages that haven't caught up yet. I really want this. Along with switch being as expression. Seems like something that JavaScript is obviously missing.
- jessaustin 6y agoI really want... switch being as expression. Unfortunately JavaScript still hasn't caught up with CoffeeScript.
- deergomoo 6y ago> Along with switch being as expression Even PHP has this now, albeit with a different keyword (match) due to existing switch semantics being pretty bad
- throwanem 6y agoOf course, you can't evaluate prose in a REPL and see for yourself what it does. I agree that overly clever code is always to be avoided. But is this really overly clever? I wouldn't expect an engineer early in their career necessarily to understand on sight what it does. But I might worry a little for one who couldn't figure it out through experiment.
- TheHalfDeafChef 6y agoI consider myself an advanced-beginner programmer untested in the real world (looking for my first coding job though!), and I understood it relatively quickly. It does appear more concise than many if-else statements. After the initial “what?”, I was able to quickly parse through it. I think this is faster than using the standard form. [edit: fixed typo]
- arielserafini 6y ago"faster" in which respect?
- TheHalfDeafChef 6y agoFaster to parse as a developer. I can't speak to whether computationally it is faster. Additionally, doesn't this somewhat also bring in the methodology of dealing with the error cases first?
- deleted 6y ago[deleted]
- t-writescode 6y agoA lot of the code you're going to have to be dealing with in the wild is going to look fine, until it breaks at 2am on a Saturday and you're 3 drinks in and production is down. You're going to hate your former cleverness then.
- tantalor 6y agoThat's why I abstain when I'm oncall (or backup). That's what the oncall bonus pay is for.
- t-writescode 6y agoReplace alcohol with 2 hours of sleep, then :)
- 6y ago
- TeMPOraL 6y agoThe only problem here is that you have to do this in the first place. This problem has been solved some 60 years ago with Lisp and `cond` construct, which is even older than `if/else if/else`! Alas, our industry likes to forget history. That said, this isn't some kind of high-level hackery. If it's cleaner than if/else if chain, you should use it. Programming is a profession. A professional is expected to learn and grow. The right answer to seeing a code construct one doesn't understand isn't throwing hands up in the air, but spending the few minutes necessary to figure out what it does.
- scotttrinh 6y agoHopefully we will get real pattern-matching at some point (https://github.com/tc39/proposal-pattern-matching https://github.com/tc39/proposal-pattern-matching), but I kinda sorta like this!
- vikingcaffiene 6y agoI’d flag this if it came up in PR. It’s clever and novel which is precisely why it’s a bad idea. Always bet on boring.
- xwolfi 6y agoExactly, because it won't be the first innovation nor the last and if you let them all through, your code becomes a bazaar of curiosities rather than a business solution...
- hatch_q 6y agoSame. I also flag seemingly good things like !!x. If you want to convert something to boolean be explicit and do Boolean(x). In most cases it turns out there was a bug in input parameter in the first place and developer was just lazy to fix it.
- kvczor 6y agoI prefer early return pattern. Which would look like this: const user = { firstName: "Seán", lastName: "Barry", email: "my.address@email.com", number: "00447123456789", }; if (!user) { throw new Error("User must be defined."); } if (!user.firstName) { throw new Error("User's first name must be defined"); } if (typeof user.firstName !== "string") { throw new Error("User's first name must be a string"); } return user;
- kvczor 6y agoLinting got messed up in the comment above, but in the actual code this is nicely readable.
- deleted 6y ago[deleted]
- elyseum 6y agoNo need to ‘return’ when you throw an error, but your approach is valid IMO: if structure / readability is that important, refactor the switch to it’s own method with only if checks in it. Hard to make that simpler and more readable.
- notyourday 6y agoIf you don't have a 'return' there's nothing to say that some time later some junior developer would not modify throw to be something else or forget that throw does not return.
- dfee 6y agoThis will prevent the JR dev from making that mistake: https://eslint.org/docs/rules/no-fallthrough https://eslint.org/docs/rules/no-fallthrough
- BiteCode_dev 6y agoThat's what tests are for.
- deleted 6y ago[deleted]
- jacknews 6y agoIs this a real thing? It looks incredibly hacky to me. What happens when multiple cases are true, are they all handled? In what order? What happens if one of them returns? Etc.
- Kinrany 6y agoThe same way switch works in every language and regardless of the clever pattern: find the first expression that equals and jump to that label.
- int_19h 6y agoA better question is: which "case" expressions are evaluated - all of them, or only the ones enumerated before the match was found? Unless you use this pattern regularly - and most coders don't, even those who mostly write JS - you'll probably have to look the answer up. That alone is a good reason to stick to if/else; there's no such ambiguity there.
- lifthrasiir 6y ago> A better question is: which "case" expressions are evaluated - all of them, or only the ones enumerated before the match was found? Good point. By the way the answer is the latter, so the following prints 1 and 2. switch (true) { case (console.log(1), false): case (console.log(2), true): case (console.log(3), false): break; }
- Kinrany 6y agoEasy enough to remember because this is the simplest thing the language can do, but yeah, I have no idea if this is the same between popular languages.
- int_19h 5y agoIn the non-Boolean case, a common implementation technique for a large switch with sequential values is a jump table. But that's only possible when all case labels are known.
- erikerikson 6y agoValidation code as shown tends to be repetitive and imperfectly implemented. I have found that transitioning to using AJV and JSON Schema is far more sustainable, especially on an API surface. One describes the data and depends on consistent and vetted logic for validating the described types rather than repetitively describing how to validate them. Validations that happened at an application level must still be written but those tend to be specific to the application logic or system state. An example of logic related validation is contingently valid argument values where the compatibility of one value being used with another must be tested. An example of state related validation is a constraint that a given value must exist in a dynamic table.
- harg 6y agoI think the post doesn't give a fair comparison as in the if/else case there's no need to use "else" blocks if you're throwing an error or returning. In this case I think simple "if" statements are cleaner and certainly more "normal". E.g. if (!user) { throw new Error("User must be defined."); } if (!user.firstName) { throw new Error("User's first name must be defined"); } return user;
- _greim_ 6y agoI'm definitely in the minority here, but I'd rather see the `else` block, since it's more explicit at-a-glance, and the logic has more symmetry with all outcomes on the same level. One reason I like RustLang is it treats this as an ergonomic issue by appending `?` for early returns, without block-nesting. So nice.
- CloselyChunky 6y agoWhen validation gets complex (e.g. there are many criteria to check), I like to build a list/stream/array (what ever the language offers) of tuples of predicates (functions from the object that gets validated to boolean) and strings (or functions from the object to string so I can have context in my error messages). Then iterate over the tuples, if a predicate fails, return the associated error message and throw an error/display the message to the user. In the end it looks something like this: var validators = Stream.of( Map.entry(user -> user != null, "User must be defined"), Map.entry(user -> user.firstName != null, "Missing first name")) validators.filter(e -> e.getKey().apply(userToBeValidated)).map(Map.Entry::getValue).getFirst() (This example uses Map.entry for tuples as Java lacks native support for tuples) This limits branching and you have all validation criteria neatly organized in the same location.
- harg 6y agoSure, if you're validating some data there're loads of better ways to do it than a bunch of conditionals. I was purely commenting on the switch(true) pattern vs some "if"s. That approach looks nice though. On that subject, JS has some nice libraries including io-ts[1] which has a functional approach using Eithers to encapsulate errors/success. [1]: https://github.com/gcanti/io-ts https://github.com/gcanti/io-ts
- borishn 6y agoThe article is misleading, in implying that `switch(true)` is a special case: "The fundamental principle of the switch true pattern is that you can match against expressions as well as values." It should be states as "The fundamental principle of the switch pattern in JavaScript is that you can match against expressions as well as values." From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...: A switch statement first evaluates its expression. It then looks for the first case clause whose expression evaluates to the same value as the result of the input expression
- graderjs 6y agoGuaranteed to always occur: ... case !isValidPhoneNumber(user.email): ... Tho see [1] and [2] [1]: https://github.com/kdeldycke/awesome-falsehood#emails https://github.com/kdeldycke/awesome-falsehood#emails [2]: https://github.com/kdeldycke/awesome-falsehood#phone-numbers https://github.com/kdeldycke/awesome-falsehood#phone-numbers
- encoderer 6y agoFor many years I stopped using this after getting flack in pull requests. I’ve recently added it back into my toolkit and am reminded how much I love it. Don’t over use it but there are some really gnarly if/else blocks that can be expressed beautifully with a switch and fall-thru.
- jbverschoor 6y agoIf (x) throw y; If (s) throw t; Throw default;
- encoderer 5y agoNope. Not the same. That is a switch with a break.
- jbverschoor 5y agoThe example in the post uses return and throw. Break doesn't matter in that case
- encoderer 5y agoI am not the author of the post. My comment specifically mentions fall thru.
- standardUser 6y agoOn a similar topic, I'm wondering how often people are using the "else" part of if/else these days. I haven't written "else" in years and I've become very fond of that "if only" pattern.
- jbverschoor 6y agoOften enough, and I use if you capture paths that should never happen logically.
- alerighi 6y agoIf you have a chain of conditions what you do? if (condition1) { // something } if (!condition1 && condition2) { // other stuff } if (!condition1 || !condition2) { // finally } An if-else is more clear: if (condition1) { // something } else if (condition2) { // other stuff } else { // finally } To me if-else is more easy to reason about (since it's clear that you enter in one of the 3 possible branches without even looking at the conditions), but also it's more efficient, especially if the condition is not a trivial comparison (for example you are comparing strings, or doing some other linear operation. And yes, computer are fast these days, but there are no excuse for wasting resources for nothing to me).
- TeMPOraL 6y agoIf that if/else chain is returning a value, then what's even more clear is: if (condition1) { return something; } if (condition2) { return otherStuff; } return finally; I don't remember the last time I wrote an if/else chain that wasn't in return position.
- hunterloftis 5y agoI agree that it is frequently a "code smell" that indicates a function should be refactored into smaller units.
- aastronaut 6y agoThis style gets the label 'poor-mans-pattern-matching' from me. If pattern matching would not be in my daily vocabulary, as it's also not available in JS, I'd consider it a misuse of switch/case and this post also makes an odd example for its usefulness. The example I would pick is the following: Consider you need to switch depending on a version (of a specification in my case), but this version isn't represented as an enum in the codebase, but as a number instead. So our team had something like this in the codebase (early return): function foobar(version: number): string { if (version === 3.1 || version === 3) { return 'result_3'; } if (version < 2 && version >= 1) { return 'result_1'; } if (version >= 2) { return 'result_2'; } throw new Error(`Cannot interpret version '${version}'`); } I read it as "people don't care about branching order that much, so how can I make my wish for better readability more clear?".... my end goal then was to bring it into this state (a distinct enum as discrete value of the version): enum Version { _1_1 = 1.1, _2 = 2, _3 = 3, _3_1 = 3.1, }; function foobar(version: Version): string { switch (version) { case Version._3_1: case Version._3: return 'result_3'; case Version._2: return 'result_2'; case Version._1_1: return 'result_1'; default: (function (val: never): never { throw new Error(`Exhaustiveness reached: ${val}`); })(version); } } ...and my interim solution that made it into the PR in time turned out to be something like this (switch true): function foobar(version: number): string { switch (true) { case version >= 3: return 'result_3'; case version >= 2: return 'result_2'; case version >= 1: return 'result_1'; default: throw new Error(`Cannot interpret version '${version}'`); } } My PR was flagged by the team for misuse of the switch statement, we had some discussion and I changed it back to the simple if/else branching from above.
- alerighi 6y agoswitch (Math.floor(version)) { case 1: return 'result_1'; case 2: return 'result_2'; case 3: return 'result_3'; default: throw new Error('...'); } Isn't that more clear?
- aastronaut 5y ago
- jypepin 6y agoI remember when I first discovered the concept of pattern matching, I tried to find a way to "hack" the switch statement in js to make it work like pattern matching. The switch(true) format was the closest I got to it, which I personally don't like compared to a clean if/else or early return. There's probably some performance differences between if/else and switch (I haven't checked) but it probably end up being what's your preference / what standard code style you want to enforce on your team.
- 1_player 6y agoThat's good and dandy until one changes one case block to normal statements instead of a terminating one, forgets to add a "break;" and someone has a nightmare debugging session trying to figure what is going on. Go did good by making case blocks break automatically and requiring the "fallthrough" keyword in one of those very rare cases you need it do.
- brundolf 6y agoIt's interesting, but I can't decide if it's an anti-pattern or not. You're abusing a construct to achieve a slight improvement in brevity/readability, with the downside of JS's lack of block-scoping for case statements which means variables in one case can conflict with variables in other cases All in all: I probably won't be using it
- jbverschoor 6y agoThe example shouldn’t be compared to esleif, that’s not how switches work. Also, because it throws you can just use if, without a block. Or use if at the end of the line if your language supports that. Way cleaner (less indentation), and less error prone. Switch statements only exist because of the underlying assembly/opcode. It just as bad as goto, because it IS goto. The cases are goto-labels. It behaves like goto, and will simply generate the same JE/JNE jumps
- deleted 6y ago[deleted]
- molszanski 6y agoIn addition to an already mentioned early return pattern, I also often recommend switching from switch to pattern matching via an object. It has an additional benefit of extracting code into data structures or making them parametric function getArrow(direction) { switch (direction) { case "left": return "<--" case "righ": return "-->" default: return "¯\\_(ツ)_/¯" } } function getArrow(direction) { const arrows = { left: "<--", right: "-->", default: "¯\\_(ツ)_/¯", } let arrow = arrows[direction] if (arrow) { return arrow } else { return arrow.default } }
- hunterloftis 5y agoI use this pattern frequently, with minor changes: function getArrow(direction) { const missing = '¯\\_(ツ)_/¯' const arrows = { left: '<--', right: '-->', } return arrows[direction] || missing }
- molszanski 5y agoYes, I use it exactly like that.
- crishoj 6y agoWhile readable and aesthetically pleasing, I find myself wondering about the performance implications of switch(true) versus a multi-branched if-else. Does V8 (and PHP) treat each construct differently when it comes to optimizations? We're not in C-land here, so jump tables are presumably not in play.
- 8128js8121 6y ago``` switch (true) { case 1 + 1 === 2: // This case evaluates to true so it will be executed default: // This will not be executed } ``` This is wrong, Since there is no return or break default will also be executed.
- deleted 6y ago[deleted]
- dvirsky 6y agoReminds me of a C++ pattern some guy I worked with years ago used to love to simplify complex if checks - using do...while(false) to create a scope you can easily break out of. e.g. bool success = false; do { if (!someCondition) break; if (!otherCondition) break; ... success = true; } while(false); if (!success) { ... } I personally disliked it, plus it can lead to funky behavior under optimization.
- deleted 6y ago[deleted]
- Igelau 6y agoWow! This guy must have been told he wasn't allowed to use goto anymore. The do-while block here is just a nameless replacement for the label.
- mwkaufma 6y agoEven accepting the dubious premise that "pretty text = maintainable code", he's juked his exampled by (i) littering the simple early-outs with unnecessary "else"s and (ii) stripping the "break"s from his switch.
- seanbarry 5y agoAuthor here. I don't think "pretty text = maintainable code". I acknowledge in my article that this pattern would be polarising (but underestimated quite how much so!). It's a pattern that IMO has a place. I personally find it more readable than multiple if blocks - but from reading the comments this seems to be very much down to personal taste. It's not a pattern that should be used in all cases by any means. You're right about the unnecessary elses - in hindsight I wish I'd put a bit more time in to the examples I used. As for the breaks, you don't need a `break` when you're returning or throwing an error in a switch case. If I had included these, someone else would have commented that they aren't needed ¯\_(ツ)_/¯
- darepublic 5y agoI prefer this pattern over long if else chains but I can never convince colleagues of this so I save this for code that I fully own
- Sophistifunk 5y agoGood lord this is awful. If somebody's paying you to solve problems with code, please just write clear code, rather than showing off. Somebody's going to have to make sense of it a year from now when requirements change, and you will be in a sense talking to that future programmer (maybe it's you) via code. You should be trying to tell them about the problem, rather than about yourself.
- SergeAx 5y ago— What bad patterns of JavaScript programming do you know? — Programming in JavaScript is a bad pattern itself.