Learning Goals 5 min
By the end of this lesson you will be able to:
- Replace a long
if/else ifchain that compares one variable against many fixed values with a cleanswitch/caseblock. - Use
break;correctly at the end of every case, and add adefault:branch to handle "anything else" — including unknown commands. - Explain why
case 'r':is valid: each case label is an integer constant, and from L01-27 you know that character literals are integer constants in disguise.
Warm-Up 10 min
L01-26's four-key remote was a small sketch with a big problem: the loop() body was a wall of if/else if. It worked — but adding a fifth command meant retyping the same pattern again, and reading it required your eyes to scan repeated keywords. Today's tool, switch, is the C++ syntax built specifically for "one variable, many fixed values".
Quick-fire puzzle
Look at this if-chain from a remote-control sketch. It's a perfectly correct piece of code, but something about it feels repetitive. What words are literally repeated from one branch to the next, and what would you ideally not have to retype?
if (c == 'r') {
setLeds(HIGH, LOW, LOW);
}
else if (c == 'y') {
setLeds(LOW, HIGH, LOW);
}
else if (c == 'g') {
setLeds(LOW, LOW, HIGH);
}- How many times does the variable name
cappear? - How many times does the operator
==appear? - If you had eight commands instead of three, how many times would each appear then?
Reveal the answer
cappears three times — once per branch.==appears three times — once per branch.- Eight times each. Every new command repeats both. The pattern is "compare the same variable to a different value", over and over.
Programmers spotted this fifty years ago and built a piece of syntax specifically for it. It's called switch. You write the variable name once at the top, then list each possible value with the syntax case X:. The chain shrinks; the intent becomes clearer. Today you learn it.
New Concept — the switch/case statement 15 min
The big idea — one variable, many fixed values
A switch statement says "look at this one value; jump to the branch labelled with the matching constant; run from there". Compare side by side:
| The old way | The same logic with switch |
|---|---|
| |
The variable c appears once. There are no == operators. Each command takes one line for the label plus its body — easier to scan, easier to add to, easier to remove from.
Anatomy of a switch
switch (c)— pick the variable being tested. Goes in normal round brackets.case 'r':— a label. Says "ifcequals this constant, jump here". Ends with a colon, not a semicolon.- The body — any number of statements, written as if you're inside the matching branch. No curly braces needed around it (though you can add them if you want a local variable).
break;— "stop running, leave the switch". Without this line, execution falls through into the next case below — usually a bug, occasionally a feature.default:— the "anything else" label, the equivalent of the finalelsein an if-chain. Goes at the bottom, runs if nocasematched.
The break rule — and why fall-through exists
Every case ends with break; in normal code. Forgetting one is the classic switch bug — the next case's body runs as well, then the one after that, until the switch hits a break or runs out. Suppose you forget the break after 'r':
case 'r':
setLeds(HIGH, LOW, LOW);
// forgot break; here!
case 'y':
setLeds(LOW, HIGH, LOW);
break;Now typing r turns the LED red then immediately yellow, with no pause. Always end each case with break; unless you have a deliberate reason not to.
The deliberate reason is stacking cases: making several constants run the same body. For example, treat both lowercase and uppercase the same way:
case 'r':
case 'R':
setLeds(HIGH, LOW, LOW);
break;Two labels, one body. Reads as "if c is either 'r' or 'R', do this". This is fall-through used on purpose, and it's the one time you leave out the break.
The default branch — handling the unexpected
Put default: at the bottom for anything not matched. It's optional, but for command-handler sketches you almost always want it — otherwise typos and stray characters disappear silently and the user has no idea what happened.
switch (c) {
case 'r': /* … */ break;
case 'g': /* … */ break;
default:
Serial.print("Unknown command: ");
Serial.println(c);
break;
}Why case 'r': is legal — the L01-27 connection
A case label must be an integer constant. case 42: is fine; case n: (a variable) is not. So why does case 'r': work? Because L01-27 revealed the secret: 'r' is an integer constant — it's the number 114, written in friendlier form. From the compiler's point of view, case 'r': and case 114: are identical. That's why switch works so cleanly for serial command parsing: the input is a char (an integer), and the case labels are character constants (also integers).
When to use switch — and when to stay with if
- Use
switchwhen you're testing one variable against several fixed constant values. The classic case: a single typed character against a set of command letters. - Stay with
iffor ranges (if (temperature > 30)), comparisons of two different things (if (a == b)), or anything involving&&/||. Switch can only do "equal to a constant".
Why it matters
Switch is the standard syntax for command dispatch in every C-family language — C, C++, C#, Java, JavaScript, Go, Rust all have it, all with this exact shape. The four-key remote you'll rebuild today uses the same syntax you'll see in operating-system code, game-input handlers, network protocol parsers, and microwave firmware. It's one of the highest-mileage tools in the whole language.
Worked Example — rebuild the traffic light with switch 20 min
You're going to take the typed traffic light from L01-26 and rewrite it with switch. Same wiring (three LEDs on D9/D10/D11, the L01-15 circuit). Same behaviour. Cleaner code. You'll also add a default: branch that finally handles unknown commands politely instead of ignoring them.
Step 1 — Rewrite the L01-26 sketch
// Typed traffic light — switch/case version
const int RED_PIN = 9;
const int YELLOW_PIN = 10;
const int GREEN_PIN = 11;
void setLeds(int r, int y, int g) {
digitalWrite(RED_PIN, r);
digitalWrite(YELLOW_PIN, y);
digitalWrite(GREEN_PIN, g);
}
void setup() {
Serial.begin(9600);
pinMode(RED_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
Serial.println("Type r, y, or g and press Send.");
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
switch (c) {
case 'r':
setLeds(HIGH, LOW, LOW);
Serial.println("RED");
break;
case 'y':
setLeds(LOW, HIGH, LOW);
Serial.println("YELLOW");
break;
case 'g':
setLeds(LOW, LOW, HIGH);
Serial.println("GREEN");
break;
default:
Serial.print("Unknown: ");
Serial.println(c);
break;
}
}
}Step 2 — Upload and verify
- Upload at 9600 baud. Open the Monitor; set line-ending to "No line ending".
- Type
rSend → red on, Monitor saysRED. - Type
ySend → yellow on, Monitor saysYELLOW. - Type
gSend → green on, Monitor saysGREEN. - Type
xSend → no LED change, but the Monitor now saysUnknown: x. This is the new behaviour thedefaultbranch unlocks: the user gets feedback when they typo.
Step 3 — Make the commands case-insensitive (deliberate fall-through)
Right now R (uppercase) triggers the default — confusing for the user. Stack the upper- and lowercase labels together to make both work the same way:
case 'r':
case 'R':
setLeds(HIGH, LOW, LOW);
Serial.println("RED");
break;The first label has no body and no break — execution falls straight through to the second label, which has both. Update all three commands the same way. Now typing R, Y, or G works just as well as the lowercase versions. This is the one and only time you deliberately leave out break.
Step 4 — Spot the deliberate bug
Make a copy of the sketch and remove the break at the end of the case 'r': body (the second one, not the stacked label). Upload. Type r. Now:
- The red LED flickers on for a split-second…
- …then immediately switches to yellow, and the Monitor prints
REDfollowed byYELLOWon the same Send.
That's fall-through gone wrong. Execution ran the red body, didn't break, fell through into the yellow body, ran that too, then finally hit yellow's break. This is what happens every time you forget a break by accident. Put the break back. The fix is one character. The bug was real.
Trace it on paper
For each typed character, predict which case runs and whether break stops execution there.
| Typed | Case label matched | Body run | Reached break? |
|---|---|---|---|
g | case 'g': | green body | yes |
Y | ____ | ____ | ____ |
x | ____ | ____ | ____ |
R (with stacked labels) | ____ | ____ | ____ |
If you can trace these without uploading, you've got switch.
Try It Yourself — three switch sketches 20 min
Goal: A polite echoer. Type any character; if it's a vowel (a, e, i, o, u — lowercase only), the Monitor replies vowel!; otherwise it replies not a vowel. No wiring.
Plan: stack the five vowel cases together so they all share one body. Everything else falls into default.
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
switch (c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
Serial.println("vowel!");
break;
default:
Serial.println("not a vowel");
break;
}
}
}Questions:
- How many
breaks are in the whole sketch and why so few? ____ (Hint: each "exit point", not each label.) - Type
A(uppercase). What does the Monitor say, and why? ____ - Add uppercase vowels so both
Aandacount. How many new labels does that need, and where do they go? ____
Goal: A digit-pin sketch. Wire one LED on D9 (L01-07 circuit). Type a digit 1–5; the Arduino blinks the LED that many times. 0 or anything else makes it print "please type 1-5".
Plan: use a switch on the typed character. Each case 1–5 sets a local int n, blinks N times in a for loop. The 0 case and default both share a "please type 1-5" message via stacking.
const int LED_PIN = 9;
void blinkN(int n) {
for (int i = 0; i < n; i = i + 1) {
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
}
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
Serial.println("Type 1-5.");
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
switch (c) {
case '1': blinkN(1); break;
case '2': blinkN(2); break;
case '3': blinkN(3); break;
case '4': blinkN(4); break;
case '5': blinkN(5); break;
default:
Serial.println("please type 1-5");
break;
}
}
}Questions:
- Compare this version with the L01-27 medium task (which used
c - '0'and oneforloop). Which feels cleaner for this exact problem — switch with five cases, orif (isDigit(c))with one calculation? Why? ____ - You want to add support for
6,7,8,9too. How many lines does that add to the switch version vs. zero lines to the L01-27 version? What lesson does that suggest about when switch is the right tool? ____ - Why is
case '1':a constant (legal) butcase c:wouldn't compile? ____
Goal: A buzzer keyboard with deliberate fall-through. Wire a piezo buzzer on D8 (L01-14 circuit). Type any of c, d, e, f, g, a, b (lowercase or upper) to play that note for 300 ms. Use stacked cases so each note has two labels (e.g. both c and C trigger middle C).
const int BUZZER_PIN = 8;
void setup() {
Serial.begin(9600);
pinMode(BUZZER_PIN, OUTPUT);
Serial.println("Keys c d e f g a b — case insensitive.");
}
void loop() {
if (Serial.available() > 0) {
char ch = Serial.read();
switch (ch) {
case 'c': case 'C': tone(BUZZER_PIN, 262, 300); break;
case 'd': case 'D': tone(BUZZER_PIN, 294, 300); break;
case 'e': case 'E': tone(BUZZER_PIN, 330, 300); break;
case 'f': case 'F': tone(BUZZER_PIN, 349, 300); break;
case 'g': case 'G': tone(BUZZER_PIN, 392, 300); break;
case 'a': case 'A': tone(BUZZER_PIN, 440, 300); break;
case 'b': case 'B': tone(BUZZER_PIN, 494, 300); break;
default:
// silently ignore — no print, no sound
break;
}
}
}Questions:
- Each note has two labels but only one
break. Why is that not a "missing break" bug? ____ (Hint: re-read the deliberate fall-through section.) - Compare the line count of this switch sketch to the L01-26 stretch (seven
else ifs, lowercase only). Which is more code? Which is easier to scan? Which is easier to add case insensitivity to? ____ - What would happen if you forgot the
breakafter the'c'note'stonecall? Predict, then try it — what notes do you hear when you typec? ____
Mini-Challenge — rebuild the four-key remote 10 min
"Same behaviour, half the noise"
Open your L01-26 mini-challenge file — the four-key remote (r/g/b/q) wired with three LEDs on D9/D10/D11 and a piezo buzzer on D8. Your job is to rewrite the loop() body using switch instead of the if/else if chain, without changing what the sketch does. Then add two improvements that switch makes easy.
Your task:
- Replace the
if/else ifchain with aswitch (c)block. - Keep the four commands working exactly as before:
rred,ggreen,bbeep,qquit. - Add case insensitivity by stacking each lowercase label with its uppercase counterpart.
- Add a
default:branch that repliesUnknown: X(where X is the character) instead of silently ignoring typos.
It works if:
- All four commands still work in lowercase, exactly as in L01-26.
- Uppercase versions (
R,G,B,Q) now work too. - Typing
xprintsUnknown: xin the Monitor.
Reveal one valid sketch
// Four-key remote — switch/case version, case-insensitive
const int RED_PIN = 9;
const int YELLOW_PIN = 10;
const int GREEN_PIN = 11;
const int BUZZER_PIN = 8;
void setLeds(int r, int y, int g) {
digitalWrite(RED_PIN, r);
digitalWrite(YELLOW_PIN, y);
digitalWrite(GREEN_PIN, g);
}
void setup() {
Serial.begin(9600);
pinMode(RED_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
Serial.println("Commands: r g b q (any case)");
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
switch (c) {
case 'r':
case 'R':
setLeds(HIGH, LOW, LOW);
Serial.println("RED");
break;
case 'g':
case 'G':
setLeds(LOW, LOW, HIGH);
Serial.println("GREEN");
break;
case 'b':
case 'B':
tone(BUZZER_PIN, 440, 200);
Serial.println("BEEP");
break;
case 'q':
case 'Q':
setLeds(LOW, LOW, LOW);
noTone(BUZZER_PIN);
Serial.println("QUIT");
break;
default:
Serial.print("Unknown: ");
Serial.println(c);
break;
}
}
}The variable c now appears once at the top of the switch. The pattern of repeated else if (c == '…') is gone. Adding a fifth command later is a six-line copy-paste — including the uppercase version and break — exactly slotting in before default. This is the canonical shape of every Cluster D project from here on.
Recap 5 min
When you're comparing one variable against many fixed values — most often a typed character against a list of commands — switch is the right tool. Write the variable once at the top; list each constant with case X:; end each branch with break;; catch typos with default:. Stack labels to make two constants run the same body. Skip break on purpose for that stacking trick, and never by accident. The whole syntax exists because L01-27's secret — 'r' is an integer constant — makes character dispatch a natural fit for switch.
switch- The statement that picks one variable and runs the branch labelled with the matching constant. Always written
switch (variable) { … }. case X:- A label inside a switch.
Xmust be an integer constant — a number like5, a character literal like'r', or aconst intname. Ends with a colon. break;- "Leave the switch now." Without it, execution falls through into the next case below. Almost every case should end with one.
- Fall-through
- What happens when a case doesn't end with
break: execution continues into the next case's body. A bug when forgotten; a feature when used on purpose to stack labels. - Stacked cases
- Two or more case labels with no body between them, sharing the body that follows. Used for "either of these constants does the same thing" — e.g. lowercase and uppercase versions of a command.
default:- The "anything not matched" label. Optional but recommended — it gives users feedback when they typo, and keeps the program from silently ignoring unknown inputs.
Homework 5 min
The vending-machine menu. Build a sketch that pretends to be a snack vending machine. Wire just the buzzer on D8 — no LEDs needed. The user types one of these single-letter commands and the Arduino responds:
| Key | Snack | Action |
|---|---|---|
c or C | Crisps | Print "DISPENSING: Crisps" and beep once at 440 Hz for 200 ms |
b or B | Biscuits | Print "DISPENSING: Biscuits" and beep twice |
w or W | Water | Print "DISPENSING: Water" and beep three times |
? | Help | Print the menu (one line per snack) |
| anything else | — | Print "Unknown command — type ? for menu" |
Use switch for the dispatch. Use stacked cases for case insensitivity. Use default: for the unknown branch. Use a small helper function beepN(int n) to handle the variable-count beeps cleanly.
Also: a design reflection on paper.
- How many
caselabels does your switch have, including the stacked ones? ____ - How many
breaks? Why fewer than the number of labels? ____ - If a real vending machine had 20 items, would you reach for switch or something else? Why? ____ (Hint: at some point the right answer is "a lookup table", not "20 cases".)
- Why is the
?command's case label writtencase '?':with single quotes — same as the letter commands? ____
Bring back next class:
- The saved
.inofile (call itvending-machine). - A phone photo of the Monitor showing yourself typing
?and getting the menu, plus one successful dispense. - Your four written reflection answers, in your notebook.
Heads up for next class: L01-29 "Serial Light Show" is the Cluster D project — it pulls together the Monitor, the input pattern, character codes, and today's switch into one extended remote-controlled LED toy. The switch you wrote today will be the heart of it.