C++ Switch
The switch statement lets you compare a single value against a fixed set of constant possibilities and run different code for each match, without writing a long chain of if/else if statements. It shines when you are branching on a known, discrete set of values, such as a menu choice, a day number, or a command character. Because the compiler can often turn a dense switch into a jump table, it can also be faster than an equivalent if/else chain once there are many cases. This lesson covers the full syntax, how switch behaves internally, fallthrough behavior, common mistakes, and best practices.
Overview / How It Works
A switch statement evaluates one controlling expression exactly once, then compares the resulting value against a series of case labels. Each case label must be a constant expression — a value known at compile time — and the type of the switch expression must be an integral type or an enumeration (int, char, short, long, bool, enum, and similar). You cannot switch directly on a std::string, a float, or a double, and case labels cannot be variables or runtime-computed expressions — only compile-time constants.
When the switch expression matches a case label, execution jumps to that label and then runs every statement after it in order, ignoring the boundaries of the other case labels, until it reaches a break statement, a return, a throw, or the closing brace of the switch block. This "falling through" into the next case is a defining feature of switch and is different from if/else, where each branch is completely isolated. If none of the case labels match, the optional default label runs; if there is no default, nothing happens and control simply continues after the switch block.
Internally, the entire switch body is a single block of code with one shared scope — the case labels are just entry points (goto-style labels) into that block, not separate scopes. This is why a variable declared under one case is technically visible to the cases below it (a fact that causes a well-known compile error, covered later). Depending on how many cases there are and how densely packed the values are, the compiler may implement the dispatch either as a series of comparisons (similar to if/else if) or as a jump table: an array of code addresses indexed directly by the case value, letting the CPU jump straight to the matching code in constant time instead of testing each case one by one. This optimization is not guaranteed by the C++ standard, but it is common in real compilers and is one reason switch can outperform a long if/else chain.
Syntax
switch (expression) {
case constant1:
// statements
break;
case constant2:
// statements
break;
default:
// statements
}
- expression — evaluated once; must produce (or convert to) an integral or enum type.
- case constantN: — a label to jump to when
expressionequalsconstantN. Each constant must be a compile-time constant and must be unique within the switch. - break; — exits the switch immediately; without it, execution falls through into the next case.
- default: — optional; runs when no case matches. It can be placed anywhere in the switch, but convention puts it last.
Examples
Example 1: Mapping a Day Number to a Name
#include <iostream>
using namespace std;
int main() {
int day = 3;
switch (day) {
case 1:
cout << "Monday" << endl;
break;
case 2:
cout << "Tuesday" << endl;
break;
case 3:
cout << "Wednesday" << endl;
break;
case 4:
cout << "Thursday" << endl;
break;
case 5:
cout << "Friday" << endl;
break;
case 6:
cout << "Saturday" << endl;
break;
case 7:
cout << "Sunday" << endl;
break;
default:
cout << "Invalid day number" << endl;
}
return 0;
}
Output:
Wednesday
The expression day is compared against each case constant. It matches case 3, so execution jumps there, prints Wednesday, and the break exits the switch before reaching any other case. The default label protects against out-of-range values like 0 or 8.
Example 2: Grouping Cases (Intentional Fallthrough) to Check Vowels
#include <iostream>
using namespace std;
int main() {
char letters[] = {'a', 'e', 'x', 'y', 'i'};
for (int i = 0; i < 5; i++) {
char c = letters[i];
switch (c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
cout << c << " is a vowel" << endl;
break;
default:
cout << c << " is a consonant" << endl;
}
}
return 0;
}
Output:
a is a vowel
e is a vowel
x is a consonant
y is a consonant
i is a vowel
Here, five case labels share the same body by stacking them with no statements in between. This is intentional fallthrough: when c equals 'a', execution falls through 'e', 'i', 'o', and 'u' labels (which have no code of their own) until it reaches the shared cout statement. This is the idiomatic way to group multiple values that should trigger the same behavior.
Example 3: A Simple Calculator Using switch
#include <iostream>
using namespace std;
int main() {
double a = 12.0;
double b = 4.0;
char op = '*';
double result = 0.0;
bool validOp = true;
switch (op) {
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
if (b != 0) {
result = a / b;
} else {
cout << "Error: division by zero" << endl;
validOp = false;
}
break;
default:
cout << "Error: unknown operator '" << op << "'" << endl;
validOp = false;
}
if (validOp) {
cout << a << " " << op << " " << b << " = " << result << endl;
}
return 0;
}
Output:
12 * 4 = 48
This example switches on a char operator to pick which arithmetic to perform — a very common real-world use of switch, similar to how a simple expression parser or calculator app might route operators. Notice the default case catches unknown operators, and the division case still uses a nested if to guard against dividing by zero, since case labels can only test for equality against constants, not arbitrary conditions.
How It Works Step by Step (Under the Hood)
When the program reaches a switch statement, the following happens:
- The controlling expression is evaluated exactly once and converted to its underlying integral (or enum) value.
- The compiler determines which
caselabel matches that value — either by generating a chain of comparisons or, for compact and dense case sets, by building a jump table indexed directly by the value for O(1) dispatch. - Execution jumps straight to the matching label, skipping everything before it in the switch body.
- Statements run sequentially from that point, with no implicit boundary between cases, until a
break,return,throw, or the end of the switch block is reached. - If no case matches and a
defaultlabel exists, execution jumps there instead; otherwise, control passes to the first statement after the switch with nothing executed.
Because the whole switch body is one block, all cases share the same variable scope. That has a direct, practical consequence covered in the next section.
Common Mistakes
Mistake 1: Forgetting break (Unintended Fallthrough)
Forgetting a break is the single most common switch bug. The code below intends to print only one message, but every case after the match also runs:
#include <iostream>
using namespace std;
int main() {
int score = 2;
switch (score) {
case 1:
cout << "Low" << endl;
case 2:
cout << "Medium" << endl;
case 3:
cout << "High" << endl;
}
return 0;
}
Output:
Medium
High
Since score is 2, execution jumps to case 2, but with no break it keeps falling through into case 3 too, printing both "Medium" and "High". The fix is to add break; at the end of every case that should not fall through:
#include <iostream>
using namespace std;
int main() {
int score = 2;
switch (score) {
case 1:
cout << "Low" << endl;
break;
case 2:
cout << "Medium" << endl;
break;
case 3:
cout << "High" << endl;
break;
}
return 0;
}
Output:
Medium
Mistake 2: Declaring a Variable Inside a Case Without Braces
Because every case shares one scope, initializing a variable in one case and using it in a later case without braces causes a compile error, since the initialization can be skipped by jumping past it:
switch (n) {
case 1:
int x = 10;
cout << x << endl;
break;
case 2:
cout << x << endl;
break;
}
Most compilers reject this with an error similar to "jump to case label crosses initialization of ‘x’", because if n is 2, control would jump straight to case 2 and use x before it was ever initialized. The fix is to wrap the case body that declares a variable in its own { } block, giving that variable a scope limited to just that case:
#include <iostream>
using namespace std;
int main() {
int n = 1;
switch (n) {
case 1: {
int x = 10;
cout << "x = " << x << endl;
break;
}
case 2:
cout << "case two" << endl;
break;
default:
cout << "default" << endl;
}
return 0;
}
Output:
x = 10
A related mistake is trying to switch on a std::string or a double — the language simply does not allow it, because case labels require compile-time integral or enum constants. For string-based branching, use an if/else if chain or a lookup structure like std::unordered_map instead.
Best Practices
- Always end a case with
break(orreturn/continue, when appropriate) unless the fallthrough is intentional. - When fallthrough is intentional, make it obvious — either leave the case body genuinely empty (as in the vowel-grouping example) or add a
// fall throughcomment, or in C++17 and later use the[[fallthrough]];attribute so both readers and the compiler’s warnings understand the omission is deliberate. - Always include a
defaultcase, even if it just logs or asserts on an unexpected value — it documents that you considered the "else" scenario. - Wrap any case body that declares its own variables in braces
{ }to give it a proper local scope and avoid the "crosses initialization" compile error. - Use
switchonly for a fixed set of discrete constant values; preferif/else iffor ranges (x > 10) or non-constant comparisons, sinceswitchcannot express those directly. - Keep case bodies short; if a case needs many lines of logic, consider calling a separate function from inside it to keep the switch readable.
Practice Exercises
- Exercise 1: Write a program that uses
switchto print the name of the month ("January", "February", …) for a given integermonthbetween 1 and 12, and prints "Invalid month" for any other value. - Exercise 2: Extend the vowel-checking example (Example 2) so that instead of printing a message per letter, it counts and prints the total number of vowels and the total number of consonants found in a fixed array of characters, using a
switchinside your loop to classify each character. - Exercise 3: Write a grade-report program that takes a
chargrade ('A','B','C','D','F') and usesswitchwith grouped cases so that'A'and'B'both print "Great job!",'C'prints "Good effort", and'D'and'F'both print "Needs improvement". Include adefaultfor invalid grades.
Summary
switchevaluates one expression once and jumps to the matchingcaselabel among a fixed set of compile-time constants.- Case labels must be integral or enum constants — not strings, floats, or runtime variables.
- Without
break, execution falls through into the next case; this is a common source of bugs, but is also a deliberate way to group cases that share behavior. - The whole switch body is one shared scope, so variables declared in a case need their own
{ }block if used only within that case. defaulthandles any value that doesn’t match a case and should almost always be included.- Dense switches can be compiled into a fast jump table, making
switcha good choice over longif/else ifchains when branching on many discrete constant values.
