Learning Goals 5 min
By the end of this lesson you will be able to:
- Wire a second push button on the breadboard, using
INPUT_PULLUPand two new wires per button — no external resistors. - Combine two conditions into one with
&&(AND — both true) and||(OR — at least one true), then put that combined condition inside anif. - Read a truth table for AND and OR, and predict the LED behaviour of an
if (a && b)sketch for any combination of button states before uploading.
Warm-Up 10 min
The 🔴 stretch task in L01-20 used && without explaining it. Today's lesson gives the two combining operators — && (AND) and || (OR) — their proper introduction, plus a second button so you have something to combine.
Quick-fire puzzle
Identify which English word — AND or OR — each rule uses. Then translate the rule into a condition.
- "You can enter the lift if you press the button and the door is open."
- "The doorbell rings if the front button is pressed or the back button is pressed."
- "Lockdown mode triggers if a window is open and motion is detected."
- "You can play the game if you're over 13 or a parent is with you."
Reveal the answer
- AND — both conditions must be true. Press alone, no lift. Door alone, no lift. Press + door = lift goes.
- OR — either condition is enough. Front rings → doorbell. Back rings → doorbell. Both ring → doorbell (still). Only "neither" results in no doorbell.
- AND — security demands both signals. A window open alone doesn't lock down; motion alone doesn't either. Both together is the alarm condition.
- OR — either qualifier admits you. The two paths are independent; one is enough.
Every rule of the form "if X and Y" or "if X or Y" maps directly to one operator. That's the whole lesson.
New Concept 20 min
The big idea — combine conditions, don't nest them
L01-19 showed you how to nest if blocks: if (X) { if (Y) { …; } } runs the body only when X is true and Y is true. That works, but it indents the code further every time you combine, and the meaning is buried.
With && you can write the same condition as a single if:
if (X && Y) {
// runs only when BOTH X and Y are true
}Flat, readable, no nesting. Same with || for "or":
if (X || Y) {
// runs when EITHER X or Y (or both) is true
}The two operators
| Operator | English name | True when… | Type to type |
|---|---|---|---|
&& | AND | BOTH sides are true | Two ampersands, no space |
|| | OR | AT LEAST ONE side is true (one or both) | Two vertical bars (pipes), no space |
! | NOT (from L01-17) | The side after it was false | One exclamation mark in front |
Truth tables — the full picture in eight rows
A truth table shows every possible combination of the inputs and what the output becomes. For two inputs each being true (T) or false (F), there are four combinations. Memorise these two tables; they cover every && and || you'll ever write.
| AND | — | OR | — | ||
|---|---|---|---|---|---|
A | B | A && B | A | B | A || B |
| F | F | F | F | F | F |
| F | T | F | F | T | T |
| T | F | F | T | F | T |
| T | T | T | T | T | T |
Notice the asymmetry: AND is true only once (when both are T); OR is false only once (when both are F). That asymmetry is the entire reason both operators exist.
Combining a comparison with a button read
The two sides of && and || can be any condition — anything that evaluates to true or false. So all of these are valid:
if (a == LOW && b == LOW) { … } // both buttons pressed
if (presses > 3 && presses < 10) { … } // in a range
if (mode == 0 || mode == 4) { … } // at either end
if (a == LOW && presses >= 5) { … } // button held AND we've had fiveThe condition can be as long as you need it. Just keep it readable — when it spans more than a line, refactor into named smaller pieces.
Short-circuit evaluation — a helpful side-effect
The Arduino is lazy in a good way. When it reaches A && B, it checks A first. If A is false, the whole && can't be true — so the Arduino skips evaluating B entirely. This is called short-circuit evaluation. With ||, it's symmetric: if A is true, the Arduino skips B because the whole expression is already true.
You don't have to think about this for Level 1, but knowing it means you can safely write conditions where the second half might fail if reached without the first half being true. The first half "guards" the second.
Wiring map — three LEDs and now two buttons
Today's hardware: the L01-10 three-LED setup, plus the L01-17/L01-19 button on D7 moved to cols 7/10, plus a new second button on D6 at cols 17/20. Five components, five signal wires, one shared GND bus.
| Component | Signal pin | Position | Wires (new today) |
|---|---|---|---|
| Red LED | D9 | cols 1–6 (signal A1) | unchanged |
| Yellow LED | D10 | cols 11–16 (signal A11) | unchanged |
| Green LED | D11 | cols 21–26 (signal A21) | unchanged |
| Button A | D7 | cols 7/10 (straddles trough) | GND→A7, D7→H7 |
| Button B (new) | D6 | cols 17/20 (straddles trough) | GND→A17, D6→H17 |
| Shared: | − rail → Arduino GND | ||
Why it matters
Almost every real-world rule involves more than one condition. "Send the email if the user is logged in and the form is valid." "Sound the alarm if a door opens or a window opens." Without && and ||, every such rule would be a tower of nested ifs. With them, the code reads almost like English.
Worked Example 20 min
Goal: build two-button hardware and write a sketch where each LED reacts to a different logical rule — one OR, one AND.
The wiring
The sketch — RED on OR, GREEN on AND
const int BUTTON_A_PIN = 7;
const int BUTTON_B_PIN = 6;
const int RED_PIN = 9;
const int GREEN_PIN = 11;
void setup() {
pinMode(BUTTON_A_PIN, INPUT_PULLUP);
pinMode(BUTTON_B_PIN, INPUT_PULLUP);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
}
void loop() {
int a = digitalRead(BUTTON_A_PIN);
int b = digitalRead(BUTTON_B_PIN);
// RED: lights when EITHER button is pressed (OR)
if (a == LOW || b == LOW) {
digitalWrite(RED_PIN, HIGH);
}
else {
digitalWrite(RED_PIN, LOW);
}
// GREEN: lights when BOTH buttons are pressed (AND)
if (a == LOW && b == LOW) {
digitalWrite(GREEN_PIN, HIGH);
}
else {
digitalWrite(GREEN_PIN, LOW);
}
}Test by truth table
Press combinations and check that the LEDs match this table — the row your fingers are creating is the only row that should be lit.
| Button A | Button B | Red LED (A || B) | Green LED (A && B) |
|---|---|---|---|
| released | released | off | off |
| pressed | released | on | off |
| released | pressed | on | off |
| pressed | pressed | on | on |
Trace one condition slowly
Press only Button A. Step through what the Arduino does on a single loop() pass:
a = digitalRead(7)→abecomesLOW.b = digitalRead(6)→bbecomesHIGH(button B released).- First
if:a == LOWis true. Because OR short-circuits on the first true, the chip doesn't even check the second half. Whole expression is true. Red LED on. - Second
if:a == LOWis true. Now AND must check the second half:b == LOWis false. So the whole AND is false. Green LED off.
Try It Yourself 20 min
Goal: Verify the truth table by pressing every combination of Button A and Button B and watching the LEDs. No code changes — just the four combinations from the table above. Note any disagreement between your prediction and reality.
Questions:
- Of the four rows in the truth table, which is the only one where both LEDs are on at the same time? ____
- Of the four rows, which is the only one where neither LED is on? ____
- Look at the bold output column for OR and for AND. Which has more Ts — and what does that tell you about how "demanding" each operator is? ____
Goal: Add the yellow LED back into the sketch with a third rule — light it when exactly one button is pressed (XOR). XOR isn't a built-in operator in C++, so we build it from &&, || and !.
The condition we want: (at least one is pressed) AND NOT (both are pressed).
// YELLOW: lights when EXACTLY one button is pressed (XOR)
if ((a == LOW || b == LOW) && !(a == LOW && b == LOW)) {
digitalWrite(YELLOW_PIN, HIGH);
}
else {
digitalWrite(YELLOW_PIN, LOW);
}Also add pinMode(YELLOW_PIN, OUTPUT); in setup().
Questions:
- Fill in the truth table for yellow:
| A | B | A || B | A && B | !(A && B) | (A || B) && !(A && B) |
|---|---|---|---|---|---|
| F | F | F | F | ____ | ____ |
| F | T | T | F | ____ | ____ |
| T | F | T | F | ____ | ____ |
| T | T | T | T | ____ | ____ |
- Which two rows light the yellow LED? Press those combinations and confirm. ____
- Can you rewrite the XOR condition without parentheses by carefully ordering it? (Honestly, no — XOR genuinely needs the grouping. Parentheses earn their keep here.)
Goal: The "key-pair lock". The green LED unlocks (lights up) only when you press a specific sequence: Button A first, then Button B (without releasing A), then let go of both. Releasing in the wrong order, or pressing them in the wrong order, doesn't unlock.
This needs state-change detection from L01-19 plus && from today. Outline:
- Track
lastAandlastBfor state change. - Track a mutable global
aWasPressedFirst(anintacting as a flag — 0 or 1). - When A goes from HIGH to LOW and B is still HIGH (released), set
aWasPressedFirst = 1. - When B goes from HIGH to LOW and A is still LOW and
aWasPressedFirst == 1, the sequence is valid: light the green LED. - When either button releases (rising edge), reset
aWasPressedFirstback to 0 and turn the LED off.
This is fiddly. Sketch the timing on paper before coding. The point isn't to produce a perfect lock — it's to use && in a non-trivial condition.
Questions:
- If you accidentally press both buttons at the exact same moment, what does your sketch decide? Which is the "first" press? ____
- How many conditions are inside the
&&chain in step 4 above? Could you simplify by introducing a named helper? ____
Mini-Challenge 15 min
The microwave panel
Real microwaves have a safety rule: the heating element only fires if the door is closed and the user has pressed Start. Open the door mid-cook → the magnetron shuts off. Press Start with the door open → nothing happens.
Today we simulate that:
- Button A = the door. Pressed = door closed; released = door open. (We pretend, since real microwave doors are switches that close when shut.)
- Button B = the Start key. Pressed = "user wants to cook".
- Green LED = the magnetron. Lit means "heat is on".
- Red LED = the warning light. Lit means "door is open, can't cook".
Your task — implement these three rules:
- Green LED on if Button A is pressed AND Button B is pressed. (Door closed AND Start pressed.)
- Red LED on if Button A is released. (Door open — warning, regardless of Start.)
- Both LEDs off if Button A is pressed AND Button B is released. (Door closed but not cooking.)
It works if:
- Door open (A released), any Start state → red LED on, green LED off.
- Door closed (A pressed), Start released → both LEDs off.
- Door closed (A pressed), Start pressed → green LED on, red LED off.
- You can't ever have both LEDs on at the same time.
Reveal one valid sketch
// Microwave safety panel — A = door (pressed = closed), B = Start key
const int DOOR_PIN = 7;
const int START_PIN = 6;
const int RED_PIN = 9;
const int GREEN_PIN = 11;
void setup() {
pinMode(DOOR_PIN, INPUT_PULLUP);
pinMode(START_PIN, INPUT_PULLUP);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
}
void loop() {
int doorClosed = (digitalRead(DOOR_PIN) == LOW);
int startPressed = (digitalRead(START_PIN) == LOW);
if (doorClosed && startPressed) {
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(RED_PIN, LOW);
}
else if (!doorClosed) {
digitalWrite(GREEN_PIN, LOW);
digitalWrite(RED_PIN, HIGH);
}
else {
digitalWrite(GREEN_PIN, LOW);
digitalWrite(RED_PIN, LOW);
}
}Notice the named intermediate variables doorClosed and startPressed. Each is the result of a comparison stored as a plain int. The if chain then reads almost like the original English specification: "if door closed AND start pressed, heat. Else if not door closed, warn. Else, idle." Naming the intermediate booleans turns a tangle of == LOW checks into something readable in one pass.
Recap 5 min
Two logical operators turn two conditions into one. && (AND) is true only when both sides are true — the demanding one. || (OR) is true when at least one side is true — the easy-going one. Combined with comparison operators and if/else if/else, you can encode almost any rule you'd describe in English. Always use the doubled form (&&, ||) for logic — the single versions exist but mean something different and rarely what you want.
- && (logical AND)
- Combines two conditions into one that's true only when both are true. Type as two ampersands, no space.
- || (logical OR)
- Combines two conditions into one that's true when at least one is true. Type as two pipes, no space.
- ! (logical NOT, from L01-17)
- Flips a true/false value.
!(a && b)is true when "a AND b" is false. - Truth table
- A complete listing of every possible input combination and the matching output. For two inputs, it has 4 rows; for three inputs, 8 rows.
- Short-circuit evaluation
- The Arduino's habit of skipping the second half of
&&or||when the first half already settles the answer. Useful for "guard" patterns. - Named intermediate
- Storing the result of a comparison in a well-named
int(likedoorClosed) so that later conditions read like English. A readability trick used heavily in real codebases.
Homework 5 min
The two-key safe. Build a sketch that simulates a safe with two security keys — both must be turned at the same time before the safe opens. Your "keys" are the two buttons.
- Hardware: today's two-button setup, plus all three LEDs.
- Behaviour:
- Both keys turned (both buttons pressed): green LED on (safe open).
- Exactly one key turned: yellow LED on (key inserted but not unlocked).
- Neither key turned: red LED on (locked, idle).
- Use named intermediates (
keyA,keyB) and the three-wayif/else if/elsechain. - Reuse the
setLedshelper from L01-20.
Also: a design reflection on paper.
- Real bank safes use TWO keys turned by TWO different people, simultaneously. Why does this design exist? Write 2–3 sentences. ____
- Write the truth table for a 3-button AND:
a && b && c. How many rows? Of those, how many give true? (Hint: think about doubling.) ____ - Your homework sketch has 2 inputs and 3 distinct outputs (green/yellow/red), each lit in exactly one of the 4 possible input combinations. Did the truth table guarantee that some output is always lit? ____
Bring back next class:
- The saved
.inofile (call ittwo-key-safe). - A short phone video (10–15 seconds) showing the LEDs change as you press combinations of the two buttons.
- Your three design-reflection answers on a notebook page.
Heads up for next class: L01-22 "Reaction Timer Game" puts everything from Cluster C together — buttons, debouncing, state-change, if/else, and a sprinkle of millis() from L01-18's stretch task — to build a proper reflex game with a timer and a score.