Evaluation of case clauses in switch statements

Why does the following return 5 instead of 7?

function clamp (num, lowerBound, upperBound) {
    switch(num) {
        case num < lowerBound:
            return lowerBound;
        case num > upperBound:
            return upperBound;
        default:
            return num;
    }
}

console.log(clamp(5,7,10));

So I must either have some stupid syntax error here that the linter didn’t catch, or I misunderstand how the expression behind the case clause is evaluated? I didn’t use break because return already breaks out of the block (I read that some people write it always out of habit/principle, but it isn’t necessary).
The result is the same if I put both the comparisons in brakcets like so:
case (num < lowerBound):

Examples of switch that I looked up use simple values (single-part expressions? what do you call these correctly?) exclusively, like 25 or 'bananas' or true. So maybe switch only works like that?

What you wrote here is a classic if/else statement that you tried to write as a switch statement. But both fulfill different tasks.

From the MDN docs:

The switch statement evaluates an expression, matching the expression’s value against a series of case clauses

The clause expects a value rather than an expression. But you provided an expression instead.

You could do the following:

function clamp(num, lowerBound, upperBound) {
    switch(num < lowerBound) {
        case true:
            return lowerBound;
        case false:
            return upperBound;
        default:
            return num;
    }
}

console.log(clamp(5,7,10)); // 7

But that does not do exactly what you want. And for readability, it would be better to follow the conventions and use an if/else statement for your task:

function clamp(num, lowerBound, upperBound) {
    if(num < lowerBound) {
    	return lowerBound;
    }
    else if(num > upperBound) {
    	return upperBound
    }
    else {
    	return num;
    }
}

console.log(clamp(5,7,10)); // 7

Or you do simply this:

function clamp(num, lowerBound, upperBound) {
    return Math.min(upperBound, Math.max(num, lowerBound))
}

console.log(clamp(5,7,10)); // 7

Thank you!

That was exactly the idea.
I already solved the problem in the exact two ways that you suggested. My question here is really just about how/if to use the switch statement.

But it doesn’t really say so, does it? The expression highlighted by you in the MDN quote refers to ‘num’ in my example (so, what comes after the switch in the () - not what comes after the case).

You are saying there can’t be an expression there after the ‘case’ - could you give me a source where it says that?

If you continue reading, it says:

and executes statements after the first case clause with a matching value

or this:

A case clause used to match against expression . If the expression matches the specified valueN

or this:

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

In switch (num), the num expression evaluates to a number.
Your cases num < lowerBound etc. evaluate to booleans (true/false).

You could edit your switch expression to avoid the mismatch,

function clamp (num, lowerBound, upperBound) {
    switch (true) {
        case num < lowerBound:
            return lowerBound;
        case num > upperBound:
            return upperBound;
        default:
            return num;
    }
}

console.log(clamp(5,7,10)); // 7
console.log(clamp(12,7,10)); // 10
console.log(clamp(8,7,10));  // 8
2 Likes

@mtrtmk:
That’s interesting.
I thought for some reason, the argument of the switch has to one of the arguments of the function declaration… So apparently there was my wrongthink.

@mirja_t : None of your quotes say it has to be a simple value. In fact,

It says right there that case clauses will also accept expressions - which are then evaluated to a simple value. My initial approach works, as proven by mtrtmk’s code - I just got the input wrong.

1 Like

Because each if clause uses a return, you could also just do straight one-liner guard clauses:

if (num > upperBound) return upperBound
if (num < lowerBound) return lowerBound
return num

For my money, that’s the second least lines of code, easiest to parse, still performant, and likely the most conventional.

1 Like

Is this syntax complete? Haven’t seen this without {} yet (and why no ; tho).

Since @mtrtmk 's code runs – yes.

And this quote

case clause whose expression evaluates

indicates that also the case clause accepts an expression, indeed.

I still don’t think it’s a very semantic approach for your intention though.

That’s just the code that would go inside the {} function body.

Semi-colon’s are rarely strictly necessary. They’re not an unreasonable convention, but interpreters do an excellent job of interpreting code without semi-colons’s and code linters/formatters usually insert them for you in the course of everyday programming.

1 Like

In JavaScript, curly braces can be omitted if the block consists of just one statement. But to avoid any mistakes/ambiguity, it is generally a good idea to include curly braces.

// Valid - With curly braces
if (num > upperBound) {
    return upperBound;
}

// Valid - Single Statement - Without curly braces
if (num > upperBound) 
    return upperBound;

// Valid 
// The single statement can be on the same line or separate line,
// but only one statement is allowed.
if (num > upperBound) return upperBound;

In javaace96747’s snippet, the single statement is on the same line as the condition, so there isn’t much ambiguity. But, when the single statement is written on a separate line, then generally curly braces are used (though some may argue that it is a matter of preference).

For a few more remarks about inclusion/omission of curly braces, see section "Description" from the documentation if...else - JavaScript | MDN where an example is given. The example starts at the text:

Not using blocks may lead to confusing behavior, especially if the code is hand-formatted.

In general, it is a good practice to always use block statements, especially in code involving nested if statements.

2 Likes

I wasn’t aware of that - I thought it applys only to (arrow) function syntax.