340

so I want to use a switch statement like this:

switch (scrollLeft) {
  case (<1000):
   //do stuff
   break;
  case (>1000 && <2000):
   //do stuff
   break;
}

Now I know that either of those statements (<1000) or (>1000 && <2000) won't work (for different reasons, obviously). What I'm asking is the most efficient way to do just that. I hate using 30 if statements, so I'd rather use the switch syntax. Is there anything that I can do?

switz
  • 24,384
  • 25
  • 76
  • 101
  • 6
    does your steps are regular ? I mean, if you divide scrollLeft by 1000, you can switch 1, 2, 3... – IcanDivideBy0 Jul 12 '11 at 14:42
  • Maybe you could make a sorted array that maps a condition range with corresponding operation, and apply a binary search on it. Or if your conditions are regular enough, you could directly call `your_mapper_object[scrollLeft / SOME_CONST]`, assuming `your_mapper_object` is something like `{1: some_func, 2: another_func, ...}`. And in this case you could also use switch. – Overmind Jiang Jul 12 '11 at 14:45
  • [Duplicate](https://stackoverflow.com/questions/5464362/javascript-using-a-condition-in-switch-case) from Mar 28, 2011 – Mr. Polywhirl Mar 01 '23 at 15:58

10 Answers10

1048

When I looked at the solutions in the other answers I saw some things that I know are bad for performance. I was going to put them in a comment but I thought it was better to benchmark it and share the results. You can test it yourself. Below are my results (ymmv) normalized after the fastest operation in each browser.

Here is the results from 2021-MAY-05

Test Chrome Firefox Opera Edge Brave Node
1.0 time 15 ms 14 ms 17 ms 17 ms 16 ms 14 ms
if-immediate 1.00 1.00 1.00 1.00 1.00 1.00
if-indirect 2.20 1.21 2.06 2.18 2.19 1.93
switch-immediate 2.07 1.43 1.71 1.71 2.19 1.93
switch-range 3.60 2.00 2.47 2.65 2.88 2.86
switch-range2 2.07 1.36 1.82 1.71 1.94 1.79
switch-indirect-array 2.93 1.57 2.53 2.47 2.75 2.50
array-linear-switch 2.73 3.29 2.12 2.12 2.38 2.50
array-binary-switch 5.80 6.07 5.24 5.24 5.44 5.37

The tests in 2021 where performed on Windows 10 64bit with the following versions: Chrome 90.0.4430.212, Firefox 89.0b13, Opera 76.0.4017.123, Edge 90.0.818.62, Brave 1.24.85, and Node 16.1.0 (was run under WSL)

Apple doesn't update Safari for Windows, so it is still 5.1.7. I changed it to Brave in this test.

Here is the results from 2012-September-04, for historical comparison:

Test Chrome Firefox Opera MSIE Safari Node
1.0 time 37 ms 73 ms 68 ms 184 ms 73 ms 21 ms
if-immediate 1.0 1.0 1.0 2.6 1.0 1.0
if-indirect 1.2 1.8 3.3 3.8 2.6 1.0
switch-immediate 2.0 1.1 2.0 1.0 2.8 1.3
switch-range 38.1 10.6 2.6 7.3 20.9 10.4
switch-range2 31.9 8.3 2.0 4.5 9.5 6.9
switch-indirect-array 35.2 9.6 4.2 5.5 10.7 8.6
array-linear-switch 3.6 4.1 4.5 10.0 4.7 2.7
array-binary-switch 7.8 6.7 9.5 16.0 15.0 4.9

The tests in 2012 where performed on Windows 7 32bit with the folowing versions: Chrome 21.0.1180.89m, Firefox 15.0, Opera 12.02, MSIE 9.0.8112, Safari 5.1.7. Node was run on a Linux 64bit box because the timer resolution on Node for Windows was 10ms instead of 1ms.

if-immediate

This is the fastest method in all tested environments, except in ... drumroll MSIE! (surprise, surprise).

This is the recommended way to implement it.

if (val < 1000) { /*do something */ } else
if (val < 2000) { /*do something */ } else
...
if (val < 30000) { /*do something */ } else

if-indirect

This is a variant of switch-indirect-array but with if-statements instead and is faster in all tested engines.

In 2021 it was 20-120% (2012: 0-280%) slower than the fastest test. Chrome takes longer time in 2021 (2.20) than in 2012 (1.2)

values=[
   1000,  2000, ... 30000
];
if (val < values[0]) { /* do something */ } else
if (val < values[1]) { /* do something */ } else
...
if (val < values[29]) { /* do something */ } else

switch-immediate

This works when you can do a calculation to get an index.

In 2021 it was 40-120% (2012: 0-180%) slower than if-immediate, except in MSIE where it actually was the fastest.

switch (Math.floor(val/1000)) {
  case 0: /* do something */ break;
  case 1: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

switch-range

It is slow because the engine has to compare the value twice for each case.

In 2021 it was 1-2.6 (2012: 1.6-38) times slower than the fastest test. Chrome has made the biggest improvement from 38 to 3.6, but is still the slowest tested engine.

switch (true) {
  case (0 <= val &&  val < 1000): /* do something */ break;
  case (1000 <= val &&  val < 2000): /* do something */ break;
  ...
  case (29000 <= val &&  val < 30000): /* do something */ break;
}

switch-range2

This is a variant of switch-range but with only one compare per case and therefore faster. The order of the case statement is important since the engine will test each case in source code order ECMAScript 2020 13.12.9

In 2021 it was 36-107% slower than the fastest test, but in 2012 it was 1-31 times slower. It is still Chrome who has the worst performance on this test, but it has improved from 32 to 2 times.

switch (true) {
  case (val < 1000): /* do something */ break;
  case (val < 2000): /* do something */ break;
  ...
  case (val < 30000): /* do something */ break;
}

switch-indirect-array

In this variant the ranges is stored in an array.

In 2021 it was 57-193% (2012: 3-35 times) slower than the fastest test. The perormance has improved in all tested engines, and while Chrome is still the slowest, it has improved from 35 to 2.93.

values=[1000,  2000 ... 29000, 30000];

switch(true) {
  case (val < values[0]): /* do something */ break;
  case (val < values[1]): /* do something */ break;
  ...
  case (val < values[29]): /* do something */ break;
}

array-linear-search

In this variant the ranges is stored in an array.

In 2021 it was 57-193% (2012: 3-35 times) slower than the fastest test. The perormance has improved in all tested engines, and while Chrome is still the slowest, it has improved from 35 to 2.93.

values=[1000,  2000 ... 29000, 30000];

for (sidx=0, slen=values.length; sidx < slen; ++sidx) {
  if (val < values[sidx]) break;
}

switch (sidx) {
  case 0: /* do something */ break;
  case 1: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

array-binary-switch

This is a variant of array-linear-switch but with a binary search. Unfortunately it is slower than the linear search. I don't know if it is my implementation or if the linear search is more optimized. It could also be that the keyspace is to small.

In 2021 this was 4-5 (2012: 4-16) times slower. Do not use.

values=[0, 1000,  2000 ... 29000, 30000];

while(range) {
  range = Math.floor( (smax - smin) / 2 );
  sidx = smin + range;
  if ( val < values[sidx] ) { smax = sidx; } else { smin = sidx; }
}

switch (sidx) {
  case 0: /* do something */ break;
  ...
  case 29: /* do something */ break;
}

Conclusion

If performance is important, use if-statements or switch, with immediate values.

some
  • 48,070
  • 14
  • 77
  • 93
  • 211
    Its rare to see an answer with this much detail and tidy structure. Big +1 – Rick Donohoe Nov 29 '12 at 14:50
  • 24
    Big +1 for the explanation of performance side of this problem! – Zoltán Schmidt Jul 23 '13 at 15:25
  • 29
    This is the reason stackoverflow is one of the best places for answers. This is a "timeless" answer, great job and thanks for the jsfiddle! – Jessy Jan 22 '14 at 13:51
  • 3
    grt info & explainination – JayKandari Feb 05 '16 at 07:52
  • 9
    I really wish I could +2, such a detailed answer! – Kaspar Lee Mar 17 '16 at 16:59
  • @some - would be awesome to update against modern browsers and mobiles. I wonder if these conditions all still apply with the new JS engines out there... – serraosays Apr 11 '19 at 03:51
  • 1
    @staypuftman I have been thinking about it... On the other hand, using `switch(true)` is usually not a good pattern, so even if the performance difference between the different methods has changed to the better, the overall conclusion will be the same. Using if-statements or switch with immediate values is cleaner code. – some Apr 21 '19 at 00:13
  • switch-range seems like an abuse of the switch statement. If you're not going to use a switch statement, just don't do a switch statement. – Anomaly Dec 11 '19 at 14:37
  • 1
    @Anomaly Yes, it is an abuse of the switch statement. I tested and included it because it was used in an answer in this thread. – some Feb 24 '20 at 13:22
  • This is a very good answer! Just a question, is it still valid in 2021? Because this one was posted in 2012 – Lee Boon Kong May 14 '21 at 15:34
  • 1
    @LeeBoonKong Yes, it is still relevant. I just updated it with data from 2021. – some May 18 '21 at 04:57
  • What is the 1.0 time ? – TmSmth Apr 08 '23 at 08:50
  • 1
    @TmSmth Instead of writing that one operation took 15 ms and another 44.ms, which was only true for the computer I had at the time, I say write that the first operation took 1.00 unit of time and the second 2.97. That way it's easier to see how much slower the other operations are. The 1.0 time is the actual time the fastest operation took on my computer. It is only useful to compare different engines on the same computer. By using unit time instead, it is easier to compare different engines and computers. – some Apr 17 '23 at 16:04
123

An alternative:

var scrollleft = 1000;
switch (true)
{
    case (scrollleft > 1000):
      alert('gt');
      break;
    case (scrollleft <= 1000):
      alert('lt');
      break; 
}

Demo: http://jsfiddle.net/UWYzr/

labue
  • 2,605
  • 3
  • 26
  • 35
  • 4
    this is a more valueable solution. +1 – IcanDivideBy0 Jul 12 '11 at 16:17
  • Nice solution to work with both regular and irregular steps. +1 – Witman Feb 28 '12 at 03:58
  • 3
    Isn't this just the same as `if(...) else if(...)`? This does avoid `if` but doesn't quite sound like a pretty replacement to me. – pimvdb Jul 19 '12 at 14:03
  • @pimvdb Very much the same. The question poster said they would "rather use the switch syntax". – labue Jul 19 '12 at 14:06
  • 12
    While elegant to code, it hurts performance. It is almost 30 times slower in Chrome than using `if`-statements. See my [answer here](http://stackoverflow.com/a/12259830/36866) – some Sep 04 '12 at 08:49
  • 1
    However such performance penalty is negligible when the data being handled is not big and maybe its a function applied only, like validating a single user input, then readability is chosen rather than performance in such case. – Jesús Franco Oct 10 '17 at 03:42
  • 1
    This is exactly what I was looking for. Thanks! – Ami Schreiber Jan 16 '18 at 18:37
  • Thanks it works, I was stuck thinking why my logic isn't working – Apoorv Pandey Apr 14 '22 at 11:44
23
switch (Math.floor(scrollLeft/1000)) {
  case 0: // (<1000)
   //do stuff
   break;
  case 1: // (>=1000 && <2000)
   //do stuff;
   break;
}

Only works if you have regular steps...

EDIT: since this solution keeps getting upvotes, I must advice that mofolo's solution is a way better

IcanDivideBy0
  • 1,627
  • 11
  • 17
  • 1
    I used `Math.round(scrollLeft/1000)` by the way. – switz Jul 12 '11 at 15:03
  • @Switz - Just keep in mind that 999 < 1000 falls into case 0 but Math.round(999/1000) falls into case 1. Also, there is a typo above, in that case 1 is >= 1000, not just >1000. – Igor Jul 12 '11 at 18:27
  • Only problem with mofolo's solution is that it is about 30 times slower in Chrome than the one by IcanDivideBy0. Se my [answer](http://stackoverflow.com/a/12259830/36866) below. – some Sep 04 '12 at 09:01
6

You can create a custom object with the criteria and the function corresponding to the criteria

var rules = [{ lowerLimit: 0,    upperLimit: 1000, action: function1 }, 
             { lowerLimit: 1000, upperLimit: 2000, action: function2 }, 
             { lowerLimit: 2000, upperLimit: 3000, action: function3 }];

Define functions for what you want to do in these cases (define function1, function2 etc)

And "evaluate" the rules

function applyRules(scrollLeft)
{
   for(var i=0; i>rules.length; i++)
   {
       var oneRule = rules[i];
       if(scrollLeft > oneRule.lowerLimit && scrollLeft < oneRule.upperLimit)
       {
          oneRule.action();
       }
   }
}

Note

I hate using 30 if statements

Many times if statements are easier to read and maintain. I would recommend the above only when you have a lot of conditions and a possibility of lot of growth in the future.

Update
As @Brad pointed out in the comments, if the conditions are mutually exclusive (only one of them can be true at a time), checking the upper limit should be sufficient:

if(scrollLeft < oneRule.upperLimit)

provided that the conditions are defined in ascending order (first the lowest one, 0 to 1000, and then 1000 to 2000 for example)

Nivas
  • 18,126
  • 4
  • 62
  • 76
  • `action=function1` -- shouldn't these be colons? ;-) -- You can also refactor this to only have an upper limit as, due to process of elimination, you can't fall within two groups--unless that was your intent (to have multiple actions possible). – Brad Christie Jul 12 '11 at 14:49
  • @Brad, no that was not my intention, and you are right, the upper limit should suffice. Will add that as an update... – Nivas Jul 13 '11 at 03:21
  • I find this one concise and clean +1 – pimvdb Jul 13 '11 at 07:33
4

This is another option:

     switch (true) {
         case (value > 100):
             //do stuff
             break;
         case (value <= 100)&&(value > 75):
             //do stuff
             break;
         case (value < 50):
            //do stuff
             break;
     }
Pablo Claus
  • 5,886
  • 3
  • 29
  • 38
4

What exactly are you doing in //do stuff?

You may be able to do something like:

(scrollLeft < 1000) ? //do stuff
: (scrollLeft > 1000 && scrollLeft < 2000) ? //do stuff
: (scrollLeft > 2000) ? //do stuff
: //etc. 
Igor
  • 33,276
  • 14
  • 79
  • 112
3

Untested and unsure if this will work, but why not do a few if statements before, to set variables for the switch statement.

var small, big;

if(scrollLeft < 1000){
    //add some token to the page
    //call it small
}


switch (//reference token/) {
  case (small):
   //do stuff
   break;
  case (big):
   //do stuff;
   break;
}
Jason Gennaro
  • 34,535
  • 8
  • 65
  • 86
1

Updating the accepted answer (can't comment yet). As of 1/12/16 using the demo jsfiddle in chrome, switch-immediate is the fastest solution.

Results: Time resolution: 1.33

   25ms "if-immediate" 150878146 
   29ms "if-indirect" 150878146
   24ms "switch-immediate" 150878146
   128ms "switch-range" 150878146
   45ms "switch-range2" 150878146
   47ms "switch-indirect-array" 150878146
   43ms "array-linear-switch" 150878146
   72ms "array-binary-switch" 150878146

Finished

 1.04 (   25ms) if-immediate
 1.21 (   29ms) if-indirect
 1.00 (   24ms) switch-immediate
 5.33 (  128ms) switch-range
 1.88 (   45ms) switch-range2
 1.96 (   47ms) switch-indirect-array
 1.79 (   43ms) array-linear-switch
 3.00 (   72ms) array-binary-switch
jeffhale
  • 3,759
  • 7
  • 40
  • 56
  • it really depends - 15ms "if-immediate" 15ms "if-indirect" 15ms "switch-immediate" 37ms "switch-range" 28ms "switch-range2" 35ms "switch-indirect-array" 29ms "array-linear-switch" 62ms "array-binary-switch" Finished 1.00 ( 15ms) if-immediate 1.00 ( 15ms) if-indirect 1.00 ( 15ms) switch-immediate 2.47 ( 37ms) switch-range 1.87 ( 28ms) switch-range2 2.33 ( 35ms) switch-indirect-array 1.93 ( 29ms) array-linear-switch 4.13 ( 62ms) array-binary-switch chrome Version 48.0.2564.109 (64-bit) mac os x 10.11.3 – RenaissanceProgrammer Feb 19 '16 at 02:20
  • ATM Safari 9.X on Mac OS x and Safari ios 9.3,"if-immediate" is the clear winner – RenaissanceProgrammer Feb 19 '16 at 02:27
  • 1
    1 ms difference is too little to care about. It vary more than that from each test run. The point is: Use the coding style that makes sense, and don't try to micro-optimize. – some Apr 19 '18 at 09:30
1

In my case (color-coding a percentage, nothing performance-critical), I quickly wrote this:

function findColor(progress) {
    const thresholds = [30, 60];
    const colors = ["#90B451", "#F9A92F", "#90B451"];

    return colors.find((col, index) => {
        return index >= thresholds.length || progress < thresholds[index];
    });
}
grebenyuksv
  • 1,729
  • 2
  • 12
  • 9
1

I hate using 30 if statements

I had the same situation lately, that's how I solved it:

before:

if(wind_speed >= 18) {
    scale = 5;
} else if(wind_speed >= 12) {
    scale = 4;
} else if(wind_speed >= 9) {
    scale = 3;
} else if(wind_speed >= 6) {
    scale = 2;
} else if(wind_speed >= 4) {
    scale = 1;
}

after:

var scales = [[4, 1], [6, 2], [9, 3], [12, 4], [18, 5]];
scales.forEach(function(el){if(wind_speed > el[0]) scale = el[1]});

And if you set "1, 2, 3, 4, 5" then it can be even simpler:

var scales = [4, 6, 9, 12, 18];
scales.forEach(function(el){if(wind_speed >= el) scale++});
Martin
  • 337
  • 2
  • 17