Loading
All articles
Programming 19 min read

The Switch Statement (Case): A Visual, Hands-On Guide

Learn the switch statement visually: how it jumps straight to a case, break and fall-through, stacked cases, enums and the compiler warnings that protect you, state machines, what C++ won't let you switch on, a real benchmark and exercises with answers.

nbkhope
On this page

Menus, game controls, days of the week, traffic lights: lots of decisions boil down to "look at this one value, and do something different for each possibility". You could write that as a long chain of if / else if. The switch statement is a tidier tool built exactly for this job.

In this guide you'll see how a switch picks its case, the famous break gotcha (and when falling through is actually handy), how enums and compiler warnings keep you safe, how to build a little state machine, and what C++ won't let you switch on. There's a real benchmark too, and exercises with answers at the end.

One value, many possible paths

An if/else chain asks its questions one at a time, top to bottom. A switch looks at a value once and goes straight to the matching case:

An if/else chain asks one by one; a switch jumps straight there. With day = 5, the if/else if chain checks day == 1, no; day == 2, no; day == 3, no; day == 4, no; day == 5, yes. The switch uses a jump table: the value 5 goes directly to case 5, out of cases 1 to 7. For cases like 1, 2, 3, the compiler can build a jump table: the value picks its case directly, like pressing an elevator button instead of stopping at every floor

Here's a switch that names the day of the week:

1#include <iostream>
2
3int main() {
4    int day;
5    std::cout << "Day of the week (1-7): ";
6    std::cin >> day;
7
8    switch (day) {
9        case 1:
10            std::cout << "Monday\n";
11            break;
12        case 2:
13            std::cout << "Tuesday\n";
14            break;
15        case 3:
16            std::cout << "Wednesday\n";
17            break;
18        case 4:
19            std::cout << "Thursday\n";
20            break;
21        case 5:
22            std::cout << "Friday\n";
23            break;
24        case 6:
25            std::cout << "Saturday\n";
26            break;
27        case 7:
28            std::cout << "Sunday\n";
29            break;
30        default:
31            std::cout << "There is no day " << day << "!\n";
32    }
33}

Three sample runs:

Day of the week (1-7): 3
Wednesday
Day of the week (1-7): 9
There is no day 9!
Day of the week (1-7): 0
There is no day 0!

The parts of a switch

The parts of a switch. switch (day) is the value to look at: an integer, char or enum. The case labels, like case 6: and case 7:, are constants, and several can share one block. break means leave the switch now. default runs when no case matches, like else
  • switch (value): the value to look at. In C++ it must be a whole-number type: int, char, an enum, and so on.
  • case 3:: a label marking where to start running when the value is 3. Labels must be constants.
  • break;: leave the switch and continue after its closing brace.
  • default:: where to go when no case matches, like the final else of an if chain. It's optional, but usually a good idea.

The big gotcha: forgetting break

A case label is just a place to jump to, not a wall. Once execution lands on a case, it keeps running downward, right through the other labels, until it reaches a break or the end of the switch. This is called fall-through:

Forget break and execution falls through into the next cases. With level = 2 and a break after every case, only case 2 runs and it prints shield. Without break, execution starts at case 2 and keeps going through case 3 and default, printing shield, dragon and everything else. A case label is just a place to jump to, not a wall. Code keeps running down until it hits a break or the closing brace
1#include <iostream>
2
3int main() {
4    int level = 2;
5
6    switch (level) {
7        case 1:
8            std::cout << "Unlocked: sword\n";
9        case 2:
10            std::cout << "Unlocked: shield\n";
11        case 3:
12            std::cout << "Unlocked: dragon\n";
13        default:
14            std::cout << "Unlocked: everything else\n";
15    }
16}
Unlocked: shield
Unlocked: dragon
Unlocked: everything else

Level 2 unlocked the dragon. Oops! Luckily, GCC warns about this when you compile with -Wextra:

1fallthrough.cpp: In function 'int main()':
2fallthrough.cpp:8:26: warning: this statement may fall through [-Wimplicit-fallthrough=]
3    8 |             std::cout << "Unlocked: sword\n";
4      |                          ^~~~~~~~~~~~~~~~~~~
5fallthrough.cpp:9:9: note: here
6    9 |         case 2:
7      |         ^~~~
8fallthrough.cpp:10:26: warning: this statement may fall through [-Wimplicit-fallthrough=]
9   10 |             std::cout << "Unlocked: shield\n";
10      |                          ^~~~~~~~~~~~~~~~~~~~
11fallthrough.cpp:11:9: note: here
12   11 |         case 3:
13      |         ^~~~
14fallthrough.cpp:12:26: warning: this statement may fall through [-Wimplicit-fallthrough=]
15   12 |             std::cout << "Unlocked: dragon\n";
16      |                          ^~~~~~~~~~~~~~~~~~~~
17fallthrough.cpp:13:9: note: here
18   13 |         default:
19      |         ^~~~~~~

When falling through is what you want

Fall-through has two good uses.

1. Several values, one action. Stack case labels with nothing between them, and they all share the same code. Here C and D both get "Okay…":

1#include <iostream>
2
3int main() {
4    char grades[] = {'A', 'B', 'C', 'D', 'F', 'Z'};
5
6    for (char grade : grades) {
7        std::cout << grade << ": ";
8        switch (grade) {
9            case 'A':
10                std::cout << "Excellent!\n";
11                break;
12            case 'B':
13                std::cout << "Great!\n";
14                break;
15            case 'C':
16            case 'D':
17                std::cout << "Okay...\n";
18                break;
19            case 'F':
20                std::cout << "Time to study together.\n";
21                break;
22            default:
23                std::cout << "Invalid grade.\n";
24        }
25    }
26}
1A: Excellent!
2B: Great!
3C: Okay...
4D: Okay...
5F: Time to study together.
6Z: Invalid grade.

A char works in a switch because a character is really a small number: 'A' is stored as 65.

2. Doing this, and then everything after it. When falling through on purpose, mark it with [[fallthrough]]; (C++17). It tells both human readers and the compiler "yes, I meant that", and the warning goes away:

1#include <iostream>
2
3int main() {
4    int secondsLeft = 3;
5
6    std::cout << "Launch in: ";
7    switch (secondsLeft) {
8        case 3:
9            std::cout << "3... ";
10            [[fallthrough]];  // on purpose: keep going into case 2
11        case 2:
12            std::cout << "2... ";
13            [[fallthrough]];
14        case 1:
15            std::cout << "1... ";
16            [[fallthrough]];
17        case 0:
18            std::cout << "Liftoff!\n";
19            break;
20    }
21}
Launch in: 3... 2... 1... Liftoff!

Switches and enums: a perfect match

Bare numbers like case 2: don't say much. An enum gives names to a fixed set of values, and enum class (C++11) keeps those names neatly grouped. Here's a game character moving around a grid:

1#include <iostream>
2
3enum class Direction { Up, Down, Left, Right };
4
5int main() {
6    int row = 5;
7    int column = 5;
8    Direction moves[] = {Direction::Up, Direction::Up, Direction::Right, Direction::Down};
9
10    for (Direction move : moves) {
11        switch (move) {
12            case Direction::Up:
13                row--;
14                break;
15            case Direction::Down:
16                row++;
17                break;
18            case Direction::Left:
19                column--;
20                break;
21            case Direction::Right:
22                column++;
23                break;
24        }
25        std::cout << "Now at row " << row << ", column " << column << "\n";
26    }
27}
1Now at row 4, column 5
2Now at row 3, column 5
3Now at row 3, column 6
4Now at row 4, column 6

case Direction::Up: reads like plain English, and you can't accidentally write case 7: for a direction that doesn't exist.

The compiler checks you covered every case

There's a bonus. When you switch on an enum without a default, the compiler knows every possible value and warns you if you forget one. Here the Right case is missing:

1enum class Direction { Up, Down, Left, Right };
2
3int columnChange(Direction move) {
4    switch (move) {
5        case Direction::Up:
6            return 0;
7        case Direction::Down:
8            return 0;
9        case Direction::Left:
10            return -1;
11    }
12    return 0;
13}

GCC with -Wall:

1missingcase.cpp: In function 'int columnChange(Direction)':
2missingcase.cpp:4:12: warning: enumeration value 'Right' not handled in switch [-Wswitch]
3    4 |     switch (move) {
4      |            ^

A switch inside a loop: state machines

Put a switch inside a loop, switch on "what state am I in?", and you've built a state machine: one of the most useful patterns in programming. Traffic lights, vending machines, game characters (idle, walking, jumping) and network connections all work this way.

A switch inside a loop: a traffic light state machine. Three states, green meaning go, yellow meaning slow down, and red meaning stop, each move to the next every tick, and red goes back to green. Each round, the switch looks at the current state, does that state's job and picks the next state. Games, vending machines and network protocols are full of these
1#include <iostream>
2
3enum class Light { Green, Yellow, Red };
4
5int main() {
6    Light light = Light::Green;
7
8    for (int tick = 1; tick <= 6; tick++) {
9        switch (light) {
10            case Light::Green:
11                std::cout << "Tick " << tick << ": GREEN  -> go\n";
12                light = Light::Yellow;
13                break;
14            case Light::Yellow:
15                std::cout << "Tick " << tick << ": YELLOW -> slow down\n";
16                light = Light::Red;
17                break;
18            case Light::Red:
19                std::cout << "Tick " << tick << ": RED    -> stop\n";
20                light = Light::Green;
21                break;
22        }
23    }
24}
1Tick 1: GREEN  -> go
2Tick 2: YELLOW -> slow down
3Tick 3: RED    -> stop
4Tick 4: GREEN  -> go
5Tick 5: YELLOW -> slow down
6Tick 6: RED    -> stop

What C++ won't let you switch on

What can go in a C++ switch? Works: switch on a number such as int, long or short; case 'A': with a char, since a character is a number; case Direction::Up: with an enum or enum class; and several labels sharing one block, like case 4: case 6:. Doesn't compile: switch on a std::string, which gives switch quantity not an integer; case 2.5: with a double or float; case limit: with a normal variable, since labels must be constants; and the same case twice, which gives duplicate case value

No strings

This is the most common surprise for people coming from other languages:

1#include <iostream>
2#include <string>
3
4int main() {
5    std::string greeting = "Hello";
6    switch (greeting) {
7        case "Hello":
8            std::cout << "Hello, dolly!\n";
9            break;
10    }
11}
1strings.cpp: In function 'int main()':
2strings.cpp:6:13: error: switch quantity not an integer
3    6 |     switch (greeting) {
4      |             ^~~~~~~~

Two good alternatives: an if/else chain (if (greeting == "Hello") …), or, when you have many pairs, a lookup table like std::map:

1#include <iostream>
2#include <map>
3#include <string>
4
5int main() {
6    std::map<std::string, std::string> replies = {
7        {"Morning", "Good morning to you, too!"},
8        {"Hello", "Hello, dolly!"},
9        {"Night", "Good night!"},
10    };
11
12    for (std::string greeting : {"Hello", "Night", "Yo"}) {
13        auto found = replies.find(greeting);
14        if (found != replies.end()) {
15            std::cout << greeting << " -> " << found->second << "\n";
16        } else {
17            std::cout << greeting << " -> What's up?\n";
18        }
19    }
20}
Hello -> Hello, dolly!
Night -> Good night!
Yo -> What's up?

No duplicate cases

1int price(int size) {
2    switch (size) {
3        case 1: return 3;
4        case 2: return 4;
5        case 2: return 5;
6    }
7    return 0;
8}
1duplicate.cpp: In function 'int price(int)':
2duplicate.cpp:5:9: error: duplicate case value
3    5 |         case 2: return 5;
4      |         ^~~~
5duplicate.cpp:4:9: note: previously used here
6    4 |         case 2: return 4;
7      |         ^~~~

Declaring a variable inside a case needs braces

This one puzzles almost everybody the first time:

1#include <iostream>
2
3int main() {
4    int choice = 2;
5    switch (choice) {
6        case 1:
7            int bonus = 10;
8            std::cout << bonus << "\n";
9            break;
10        case 2:
11            std::cout << "two\n";
12            break;
13    }
14}
1crosses.cpp: In function 'int main()':
2crosses.cpp:10:14: error: jump to case label
3   10 |         case 2:
4      |              ^
5crosses.cpp:7:17: note:   crosses initialization of 'int bonus'
6    7 |             int bonus = 10;
7      |                 ^~~~~

The whole switch is one block, so bonus would still exist in case 2, but jumping there skips its initialization. The fix is to give the case its own block with braces: case 1: { int bonus = 10; … break; }. You'll see this in the days-in-month answer below.

Is a switch faster than if/else?

You'll often hear that a switch is faster because of its jump table. We tested it: the same 16-way decision on 50 million random values, written both ways, with each case calling a different function:

switch vs. if/else chain: the compiler evens it out. The same 16-way decision on 50 million random values, each case calling a different function, measured for this article with GCC 14.1, median run, on one Windows laptop. With no optimization, -O0, where only the switch gets a jump table: switch 787.1 milliseconds, if/else chain 850.9 milliseconds. Optimized, -O2, where both become jump tables: switch 489.6 milliseconds, if/else chain 473.3 milliseconds. With optimization on, GCC turned the if/else chain into the very same jump table as the switch, so the times are within measurement noise. Pick whichever reads better

Looking at the machine code GCC produced, the optimized build used a jump table for both versions: the compiler recognized that the if/else chain was really a switch in disguise. Without optimization, only the switch got one.

Which one should you use?

switch, if/else or a lookup table? switch: one value compared with a list of fixed values, like menu options and enum states. if / else if: ranges and complex tests, like score >= 90 or age < 13 && …, and anything with strings. std::map: many value-to-answer pairs, or data that changes while the program runs

The same idea in other languages

Many languages can switch on strings. JavaScript works just like C++, plus strings:

1const greeting = "Hello";
2switch (greeting) {
3  case "Morning":
4    console.log("Good morning to you, too!");
5    break;
6  case "Hello":
7    console.log("Hello, dolly!");
8    break;
9  default:
10    console.log("What's up?");
11}

Python (3.10 and later) has match, which never falls through and uses | for "either of these":

1greeting = "Hello"
2match greeting:
3    case "Morning":
4        print("Good morning to you, too!")
5    case "Hello" | "Hi":
6        print("Hello, dolly!")
7    case _:
8        print("What's up?")

Both print Hello, dolly!.

LanguageSwitch on strings?Falls through without break?
C, C++NoYes
Java, JavaScript, PHPYesYes
C#YesNo: it's a compile error to fall out of a non-empty case
Python (match)YesNo
GoYesNo (unless you write fallthrough)

Practice time

Try these before peeking at the answers. Copy each program into a file, compile with g++ -std=c++20 -Wall -Wextra program.cpp, and run it.

Exercise 1: Predict the output (warm-up). What does this print?

1int number = 2;
2switch (number) {
3    case 1:
4        std::cout << "one ";
5    case 2:
6        std::cout << "two ";
7    case 3:
8        std::cout << "three ";
9        break;
10    case 4:
11        std::cout << "four ";
12}

Exercise 2: Pocket calculator. Given two numbers and an operator character ('+', '-', '*' or '/'), print the result. Anything else should print unknown operation.

Exercise 3: Count the vowels. Using a switch with stacked cases, count the vowels in "switch statements are fun".

Exercise 4: Days in a month. Write int daysInMonth(int month, int year). April, June, September and November have 30 days; February has 28, or 29 in a leap year; the rest have 31.

Exercise 5: Rewrite it. Turn this into a switch. Which version do you find easier to read?

1if (key == 'w') moveUp();
2else if (key == 's') moveDown();
3else if (key == 'a') moveLeft();
4else if (key == 'd') moveRight();
5else beep();

Exercise 6: A coin-operated turnstile (challenge). A turnstile starts Locked. A coin unlocks it; a push lets one person through and locks it again. Pushing a locked turnstile does nothing, and a coin in an unlocked one is just a tip. Feed it the events push, coin, push, push, coin, coin and print what happens.

Exercise 6: a coin-operated turnstile. Two states, Locked and Unlocked. A coin moves Locked to Unlocked; a push moves Unlocked back to Locked. Pushing while locked stays locked, and a coin while unlocked stays unlocked

Quick quiz

  1. What happens if you leave out break at the end of a case?
  2. Can you switch on a std::string in C++? What can you do instead?
  3. Why does switch (grade) work when grade is a char?
  4. When might you leave out default on purpose?
  5. What does [[fallthrough]]; do?

Answers

Exercise 1: two three. It jumps to case 2, prints "two ", falls through into case 3, prints "three ", then hits break.

two three 

Exercise 2:

1#include <iostream>
2
3int main() {
4    double first = 12;
5    double second = 4;
6    char operations[] = {'+', '-', '*', '/', '%'};
7
8    for (char operation : operations) {
9        std::cout << first << " " << operation << " " << second << " = ";
10        switch (operation) {
11            case '+':
12                std::cout << first + second << "\n";
13                break;
14            case '-':
15                std::cout << first - second << "\n";
16                break;
17            case '*':
18                std::cout << first * second << "\n";
19                break;
20            case '/':
21                std::cout << first / second << "\n";
22                break;
23            default:
24                std::cout << "unknown operation\n";
25        }
26    }
27}
112 + 4 = 16
212 - 4 = 8
312 * 4 = 48
412 / 4 = 3
512 % 4 = unknown operation

Exercise 3: Five stacked labels share one vowels++:

1#include <iostream>
2#include <string>
3
4int main() {
5    std::string sentence = "switch statements are fun";
6    int vowels = 0;
7    for (char letter : sentence) {
8        switch (letter) {
9            case 'a':
10            case 'e':
11            case 'i':
12            case 'o':
13            case 'u':
14                vowels++;
15                break;
16        }
17    }
18    std::cout << "Vowels: " << vowels << "\n";
19}
Vowels: 7

Exercise 4: Stacked cases for the 30-day months, braces around February's block because it declares a variable, and default for the 31-day months:

1#include <iostream>
2
3int daysInMonth(int month, int year) {
4    switch (month) {
5        case 4:
6        case 6:
7        case 9:
8        case 11:
9            return 30;
10        case 2: {
11            bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
12            return isLeapYear ? 29 : 28;
13        }
14        default:
15            return 31;
16    }
17}
18
19int main() {
20    std::cout << "January 2025:  " << daysInMonth(1, 2025) << "\n";
21    std::cout << "April 2025:    " << daysInMonth(4, 2025) << "\n";
22    std::cout << "February 2025: " << daysInMonth(2, 2025) << "\n";
23    std::cout << "February 2024: " << daysInMonth(2, 2024) << "\n";
24}
1January 2025:  31
2April 2025:    30
3February 2025: 28
4February 2024: 29

return leaves the whole function, so these cases don't need break.

Exercise 5:

1switch (key) {
2    case 'w': moveUp(); break;
3    case 's': moveDown(); break;
4    case 'a': moveLeft(); break;
5    case 'd': moveRight(); break;
6    default: beep();
7}

Most people find the switch easier to scan: every option lines up, and it's obvious that they all test the same key.

Exercise 6: A switch on the state, with an if inside each case for the event:

1#include <iostream>
2#include <string>
3
4enum class State { Locked, Unlocked };
5
6int main() {
7    State state = State::Locked;
8    std::string events[] = {"push", "coin", "push", "push", "coin", "coin"};
9
10    for (const std::string& event : events) {
11        switch (state) {
12            case State::Locked:
13                if (event == "coin") {
14                    state = State::Unlocked;
15                    std::cout << event << ": unlocked, come on through\n";
16                } else {
17                    std::cout << event << ": still locked, insert a coin\n";
18                }
19                break;
20            case State::Unlocked:
21                if (event == "push") {
22                    state = State::Locked;
23                    std::cout << event << ": you passed, locked again\n";
24                } else {
25                    std::cout << event << ": already unlocked, thanks for the tip\n";
26                }
27                break;
28        }
29    }
30}
1push: still locked, insert a coin
2coin: unlocked, come on through
3push: you passed, locked again
4push: still locked, insert a coin
5coin: unlocked, come on through
6coin: already unlocked, thanks for the tip

Quiz: (1) Execution falls through into the next case's code, and keeps going until a break or the end of the switch. (2) No; use an if/else chain or a lookup table such as std::map. (3) A char is a small whole number underneath ('A' is 65). (4) When switching on an enum, so the compiler warns you if a value isn't handled. (5) It marks a fall-through as intentional, for readers and for the compiler, which then doesn't warn.

Cheat sheet

I want to…Write
Pick an action by valueswitch (value) { case 1: …; break; case 2: …; break; }
Handle "anything else"default: …
Give several values the same actioncase 'C': case 'D': …; break;
Fall through on purpose[[fallthrough]]; before the next label
Use readable namesenum class Direction { Up, Down }; then case Direction::Up:
Get warned about missing enum casesswitch on the enum with no default, compile with -Wall
Declare a variable inside a casewrap it in braces: case 1: { int bonus = 10; … break; }
Choose by stringif (text == "Hello") … else if … or a std::map
Build a state machinea switch (state) inside a loop that updates state

Wrapping up

The switch statement is a clean way to say "look at this value and pick the matching path":

  • A switch jumps to the matching case, and default catches everything else.
  • break matters: without it, execution falls through into the cases below. -Wextra warns you, and [[fallthrough]] marks the times you mean it.
  • Stacked labels give several values the same action.
  • Enums and switches go together: readable names, plus a warning when a value isn't handled.
  • A switch in a loop makes a state machine, the heart of games, devices and protocols.
  • C++ switches only work on whole numbers, chars and enums. For strings, use if/else or a std::map.
  • Speed is a tie: with optimization on, GCC built the same jump table for an if/else chain. Choose the one that reads best.

Keep going with The If Statement, The For Loop and The While and Do-While Loops.

Learning the C++ programming language? How about doing it with One Hour a Day.

Thanks for reading!

Know someone who would enjoy this? Send it their way.

More articles

Read next

Comments

Be kind and specific. Comments with links from new accounts appear after a moderator reviews them.