Pre-COSMOS Day 6, for Cluster 10: Robot Inventors, running two hours or more in depth. It covers comparisons and booleans, then if, elif, and else, where the first true branch wins and so the order matters, and and, or, and not, including the classic "or green" bug and the == against = trap. It goes on to more advanced techniques - the in operator, chained comparisons, truthiness, operator precedence, and De Morgan's law - then builds the Maze Robot Brain v1. It ends with finite state machines done two ways: as nested if/elif, and table-driven, using dictionary dispatch together with a transition table mapping (state, input) to (action, next_state). Every snippet is runnable, and the outputs and error messages came verbatim from CPython 3.12.
Subject: Python · 110 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Objectives
Robots decide constantly - if I see red, turn. Today you make programs choose. By the end you can:
1. Compare values with ==, !=, <, >, <=, >= and read the True/False they return.
2. Choose between paths with if / elif / else, knowing the first true branch wins.
3. Combine tests with and, or, not - and avoid the classic or bug.
4. Build the Maze Robot Brain: read a color and decide a move.
5. Explain a finite state machine - states plus rules for switching - and build a patrol/guard robot.
6. Reach for sharper tools - in, chained comparisons, truthiness - and build a table-driven state machine.
Warm-up
Discussion prompt
Before we open Decisions: if / elif / else & Finite-State Thinking: without looking back, what was the main idea of List Methods: append, sort, len, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
An in-depth session of 36 slides. append() adds in place, sort() and sort(reverse=True) reorder in place, and len() counts, which leads to the big idea that in-place methods return None, so nums = nums.sort() wipes your data. It also covers append against +, and top-N slicing, with three traps and five checks, then the four-part High-Score Board build with a top-three stretch. It was built for the Pre-COSMOS Day 4 hour.
Concept
Up to now your code ran straight through, top to bottom. A decision lets it choose: do this only when something is true.
A maze robot does this every moment: if I see green, go forward; if I see red, turn. That choice is what you are about to write.
Counterexample
Discussion prompt
Up to now your code ran straight through, top to bottom. A decision lets it choose: do this only when something is true.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
A maze robot does this every moment: if I see green, go forward; if I see red, turn. That choice is what you are about to write.
Concept
Every decision rests on a condition - an expression that is either True or False (a bool). color == "red" asks: is color equal to red?
condition — A True/False expression a decision is based on. The code under an if runs only when its condition is True.
Analogy
Discussion prompt
Explain A condition is a yes/no question by analogy to something with no Python in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Every decision rests on a condition - an expression that is either True or False (a bool). color == "red" asks: is color equal to red?
Concept
These six operators each ask a yes/no question and return a bool:
| operator | asks | example | value |
|---|---|---|---|
| == | equal to? | 5 == 5 | True |
| != | not equal to? | 5 != 6 | True |
| < | less than? | 5 < 6 | True |
| > | greater than? | 3 > 9 | False |
| <= | at most? | 7 <= 7 | True |
| >= | at least? | 7 >= 8 | False |
Comparison
Comparison matrix
From Comparison operators: refill the asks column from what you know. The rest of the table is as it appeared.
| operator | asks | example | value |
|---|---|---|---|
| == | equal to? | 5 == 5 | True |
| != | not equal to? | 5 != 6 | True |
| < | less than? | 5 < 6 | True |
| > | greater than? | 3 > 9 | False |
| <= | at most? | 7 <= 7 | True |
| >= | at least? | 7 >= 8 | False |
Pattern
Predict first
The table runs: 5 == 5 | True · 5 != 6 | True · 3 > 9 | False
In Comparisons return True or False, given the rows so far: what is the next one — the row where expression is 7 >= 7?
Correct: 7 >= 7 | True
| expression | value |
|---|---|
| 5 == 5 | True |
| 5 != 6 | True |
| 3 > 9 | False |
| 7 >= 7 | True |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. A comparison is a question; its answer is True or False, which you can print, store, or test in an if.
Worked example
print(5 == 5)
print(5 != 6)
print(3 > 9)
print(7 >= 7)Each comparison evaluates to a bool
Why: A comparison is a question; its answer is True or False, which you can print, store, or test in an if.
Read the answers
Why: Verified by execution: True, True, False, True.
| expression | value |
|---|---|
| 5 == 5 | True |
| 5 != 6 | True |
| 3 > 9 | False |
| 7 >= 7 | True |
Discrimination
Sort into buckets
Sort these by value, from memory, without looking back at Comparisons return True or False. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Trying to check if color is red - with a single =.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Python stops before running anything - it even guesses your mistake in the error message.
Use == to compare. One = sets a value; two == ask if values are equal.
Why: Python stops before running anything - it even guesses your mistake in the error message.
Trap
Trying to check if color is red - with a single =.
color = "red"
if color = "red":
print("turn")A single = means assign, and you can't assign inside an if
Why: Python stops before running anything - it even guesses your mistake in the error message.
| expression | result |
|---|---|
| if color = "red": | SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? |
Use == to compare. One = sets a value; two == ask if values are equal.
color = "red"
if color == "red":
print("turn")== asks the yes/no question
Why: color == "red" is True, so the branch runs. Real output: turn. Read it aloud as 'is color equal to red?'
| expression | value | output |
|---|---|---|
| color == "red" | True | turn |
Error analysis
Annotate
Walk the callouts on Trap: == compares, = assigns. Each one is a place this is easy to get subtly wrong.
Concept
== checks for an exact match. "Red" is not equal to "red" - a capital letter is a different character.
Types matter too: the number 5 is not equal to the text "5". This is why a sensor reading must be the right type before you compare it.
Explain it
Discussion prompt
Explain == is exact: case and type matter to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
== checks for an exact match. "Red" is not equal to "red" - a capital letter is a different character.
Worked example
print("red" == "red")
print("Red" == "red")
print("red" == "blue")Only an exact, same-case match is True
Why: Verified by execution: "Red" differs from "red" by one capital letter, so it is False.
| expression | value | why |
|---|---|---|
| "red" == "red" | True | identical |
| "Red" == "red" | False | capital R differs |
| "red" == "blue" | False | different words |
Trade off
Comparison matrix
From Comparing strings: every row here is a choice with a cost. Fill the why column, then say which row you would actually pick and what you give up for it.
| expression | value | why |
|---|---|---|
| "red" == "red" | True | identical |
| "Red" == "red" | False | capital R differs |
| "red" == "blue" | False | different words |
Concept
if runs a block of code only when its condition is True. Write if, the condition, a colon, then the indented lines to run.
If the condition is False, the indented block is skipped entirely and the program moves on.
Intuition
Picture each if as a gate on the path. The condition is the lock. If the condition is True the gate opens and you walk through the indented block; if it is False the gate stays shut and you walk around it.
The indented lines are behind the gate. That is why indentation matters: it marks exactly what is inside.
Worked example
temp = 80
if temp > 50:
print("warm")
print("done")Line 2: the condition temp > 50 is True
Why: 80 > 50, so the gate opens and the indented line runs.
Line 4 runs no matter what - it is NOT indented
Why: Verified by execution: prints warm then done. 'done' is outside the if, so it always runs.
| line | runs? | prints |
|---|---|---|
| if temp > 50 | condition True | - |
| print("warm") | yes (inside) | warm |
| print("done") | always | done |
Pattern
Step through it
Step through A simple if one row at a time. What is driving the change, and what would the row after the last one be?
Concept
else gives an otherwise path: run this when the if condition was False. It has no condition of its own - it catches everything the if missed.
Worked example
temp = 30
if temp > 50:
print("warm")
else:
print("cold")The condition is False, so else runs
Why: 30 > 50 is False, so the if block is skipped and the else block runs. Verified by execution: prints cold.
| temp | temp > 50 | prints |
|---|---|---|
| 80 | True | warm |
| 30 | False | cold |
Concept
When there are several possibilities, elif (else-if) adds more conditions between the if and the else. You can have as many elif branches as you need.
Python checks them top to bottom and takes the first one that is True.
Fill the middle
Fill in the blanks
From if / elif / else with a color — one line has had its right-hand side removed. Put it back.
color = "red"
if color == "green":
print("go")
elif color == "red":
print("turn")
else:
print("wait")
Why: color is what everything below it consumes, so the wrong expression here fails later and somewhere else. color is "red", so color == "green" is False.
Worked example
color = "red"
if color == "green":
print("go")
elif color == "red":
print("turn")
else:
print("wait")Check the if first: is color green?
Why: color is "red", so color == "green" is False. Skip that block.
Check the elif: is color red?
Why: Yes - so it prints turn, and the else is skipped entirely.
| branch | condition | taken? |
|---|---|---|
| if color == "green" | False | no |
| elif color == "red" | True | yes -> turn |
| else | (not reached) | no |
Blank canvas
Draw it
Draw what if / elif / else with a color just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Concept
Each condition line ends with a colon :, and the lines that belong to that branch are indented beneath it (four spaces is standard).
Same indentation = same block. Python uses the spacing itself to know where a branch begins and ends - so line up your code carefully.
Concept
An if / elif / else chain runs exactly one branch - the first whose condition is True, or the else if none match. Then it skips the rest.
It is not many separate ifs; it is one decision with several doors, and you go through only one.
Worked example
color = "blue"
if color == "green":
print("forward")
elif color == "red":
print("turn")
else:
print("stop")Neither the if nor the elif matches
Why: "blue" is not green and not red, so both conditions are False.
The else catches it
Why: Verified by execution: prints stop - and only stop. The forward and turn lines never run.
| branch | condition | ran? |
|---|---|---|
| if green | False | no |
| elif red | False | no |
| else | fallback | yes -> stop |
Pattern
Step through it
Step through Watching the others get skipped one row at a time. What is driving the change, and what would the row after the last one be?
Concept
Because Python takes the first true branch and stops, the order of your conditions changes the result. A broad condition placed too early can swallow the specific ones below it.
Intuition
Think of the branches as a checklist you read from the top. At the first box you can tick 'yes', you stop and do that action - you never read further.
So the most specific question must come first. Put the widest, easiest-to-pass condition last, or it grabs everything before the picky ones get a turn.
Worked example
score = 85
if score >= 90:
print("A")
elif score >= 80:
print("B")
elif score >= 70:
print("C")Strictest test first: >= 90
Why: 85 >= 90 is False, so skip A.
Next: >= 80
Why: Verified by execution: 85 >= 80 is True, so it prints B and stops. Highest threshold first is the correct order.
| branch | 85 passes? | taken? |
|---|---|---|
| score >= 90 | no | no |
| score >= 80 | yes | yes -> B |
| score >= 70 | (not reached) | no |
Pattern
Step through it
Step through Grades in the right order one row at a time. What is driving the change, and what would the row after the last one be?
Anomaly
Predict first
A student writes this, and it looks reasonable:
Same grades, but the easiest condition is checked first.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: A 95 should be an A, but >= 70 is true first and Python stops there.
Put the strictest condition first so it gets the first chance.
Why: A 95 should be an A, but >= 70 is true first and Python stops there. The >= 90 test never runs.
Trap
Same grades, but the easiest condition is checked first.
score = 95
if score >= 70:
print("C")
elif score >= 80:
print("B")
elif score >= 90:
print("A")95 passes >= 70 immediately, so it prints C
Why: A 95 should be an A, but >= 70 is true first and Python stops there. The >= 90 test never runs.
| branch | 95 passes? | taken? |
|---|---|---|
| score >= 70 | yes | yes -> C (wrong!) |
| score >= 80 | (not reached) | no |
| score >= 90 | (not reached) | no |
Put the strictest condition first so it gets the first chance.
score = 95
if score >= 90:
print("A")
elif score >= 80:
print("B")
elif score >= 70:
print("C")Highest threshold first
Why: Now 95 >= 90 is true first, so it correctly prints A. Verified by execution. Order conditions from most specific to least.
| branch | 95 passes? | taken? |
|---|---|---|
| score >= 90 | yes | yes -> A |
Break the constraint
Discussion prompt
The rule this trap just fixed:
Put the strictest condition first so it gets the first chance.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
A 95 should be an A, but >= 70 is true first and Python stops there. The >= 90 test never runs.
Concept
and combines two conditions and is True only when both are True. If the robot is on AND the path is clear, move.
Worked example
temp = 40
if temp > 30 and temp < 50:
print("in range")
else:
print("out of range")Both halves must hold
Why: Verified by execution: 40 > 30 is True AND 40 < 50 is True, so the whole condition is True - prints in range.
| temp > 30 | temp < 50 | and result |
|---|---|---|
| True | True | True -> in range |
| True (60) | False (60) | False -> out of range |
Concept
or is True when at least one side is True (it is only False when both are False). If I see red OR yellow, slow down.
Fill the middle
Fill in the blanks
From or in action — one line has had its right-hand side removed. Put it back.
color = "yellow"
if color == "red" or color == "yellow":
print("slow down")
else:
print("keep going")
Why: color is what everything below it consumes, so the wrong expression here fails later and somewhere else. Verified by execution: color == "red" is False, but color == "yellow" is True, so or is True - prints slow down.
Worked example
color = "yellow"
if color == "red" or color == "yellow":
print("slow down")
else:
print("keep going")One true side is enough
Why: Verified by execution: color == "red" is False, but color == "yellow" is True, so or is True - prints slow down.
| color == red | color == yellow | or result |
|---|---|---|
| False | True | True -> slow down |
| False | False | False -> keep going |
Concept
not reverses a condition. not True is False; not False is True. If the path is NOT clear, stop.
Worked example
clear = False
if not clear:
print("stop")not flips the value
Why: Verified by execution: clear is False, so not clear is True, and it prints stop.
| clear | not clear | prints |
|---|---|---|
| False | True | stop |
| True | False | (nothing) |
Concept
The whole logic of and and or, side by side:
| A | B | A and B | A or B |
|---|---|---|---|
| True | True | True | True |
| True | False | False | True |
| False | True | False | True |
| False | False | False | False |
And not simply flips: not True is False, not False is True.
Pattern
Step through it
Step through Truth table: and / or / not one row at a time. What is driving the change, and what would the row after the last one be?
Estimation
Predict first
Move forward only when the way is clear AND the battery is not empty.
Commit before you compute: what does Combining tests for a robot rule come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Both parts checked together
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Verified by execution: clear is True and 20 > 10 is True, so it prints forward.
Worked example
Move forward only when the way is clear AND the battery is not empty.
clear = True
battery = 20
if clear and battery > 10:
print("forward")
else:
print("hold")Both parts checked together
Why: Verified by execution: clear is True and 20 > 10 is True, so it prints forward. If either were False, it would hold.
| clear | battery > 10 | and -> action |
|---|---|---|
| True | True | True -> forward |
| True | False (5) | False -> hold |
| False | True | False -> hold |
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Both parts checked together
What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.
Hint: Every quantity in the result had to enter somewhere. Account for each one.
Answer:
Move forward only when the way is clear AND the battery is not empty.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Trying to check if the color is red or green - the tempting short way.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: The bare string "green" is always truthy, so the whole condition is ALWAYS true.
Spell out a full comparison on each side of or.
Why: The bare string "green" is always truthy, so the whole condition is ALWAYS true. It prints even for blue - a silent, dangerous bug.
Trap
Trying to check if the color is red or green - the tempting short way.
color = "blue"
if color == "red" or "green":
print("red or green")Python reads this as (color == "red") or ("green")
Why: The bare string "green" is always truthy, so the whole condition is ALWAYS true. It prints even for blue - a silent, dangerous bug.
| color | condition is | prints? |
|---|---|---|
| blue | always True | yes (wrong!) |
| anything | always True | yes (wrong!) |
Spell out a full comparison on each side of or.
color = "blue"
if color == "red" or color == "green":
print("red or green")
else:
print("neither")Each side is its own complete test
Why: Verified by execution: "blue" is neither, so it prints neither. Rule: never write or "value" - always write or variable == "value".
| color | condition is | prints |
|---|---|---|
| blue | False or False | neither |
| green | False or True | red or green |
Comparison
Comparison matrix
From Trap: the "or green" bug: refill the prints? column from what you know. The rest of the table is as it appeared.
| color | condition is | prints? |
|---|---|---|
| blue | always True | yes (wrong!) |
| anything | always True | yes (wrong!) |
Concept
Checking many values with or gets long - and invites the bare-or bug. color in ["red", "green", "blue"] asks is color one of these? in a single, safe expression.
It returns True if the value is somewhere in the list, False otherwise - no repeated ==, no always-true trap.
Fill the middle
Fill in the blanks
From in fixes the or bug — one line has had its right-hand side removed. Put it back.
color = "blue"
if color in ["red", "green"]:
print("warm color")
else:
print("not red or green")
Why: color is what everything below it consumes, so the wrong expression here fails later and somewhere else. Verified by execution: "blue" is not in ["red", "green"], so it prints not red or green.
Worked example
color = "blue"
if color in ["red", "green"]:
print("warm color")
else:
print("not red or green")in checks membership against the whole list at once
Why: Verified by execution: "blue" is not in ["red", "green"], so it prints not red or green. This is the clean replacement for color == "red" or color == "green".
| expression | value |
|---|---|
| "green" in ["red", "green", "blue"] | True |
| "yellow" in ["red", "green", "blue"] | False |
| "blue" in ["red", "green"] | False |
Concept
Python lets you chain comparisons the way math does: 0 <= score <= 100 means score is between 0 and 100. It reads like the math and needs no and.
80 <= score < 90 is the cleanest way to test a band - one expression instead of score >= 80 and score < 90.
Worked example
score = 85
print(0 <= score <= 100)
print(80 <= score < 90)
print(90 <= score < 100)Each chain is one True/False answer
Why: Verified by execution: 85 is inside 0..100 (True), inside the 80s band (True), but not the 90s (False).
| expression | value | meaning |
|---|---|---|
| 0 <= score <= 100 | True | in valid range |
| 80 <= score < 90 | True | a B (the 80s) |
| 90 <= score < 100 | False | not an A |
Concept
An if doesn't need a comparison - any value works. Python asks is this truthy? Recall from Day 2: 0, "" (empty text), and empty lists are falsy; everything else is truthy.
So if name: means if name is not empty - a clean way to check the robot actually received some input.
Worked example
name = ""
if name:
print("hello", name)
else:
print("no name given")An empty string is falsy, so the else runs
Why: Verified by execution: name is "", which is falsy, so it prints no name given. With name = "Robo" the truthy if branch would run instead.
| name | truthy? | branch |
|---|---|---|
| "" | False | else -> no name given |
| "Robo" | True | if -> hello Robo |
Concept
When you mix them, Python applies not first, then and, then or. So True or False and False is read as True or (False and False) = True.
When in doubt, add parentheses. (a or b) and c is clearer than trusting the order - and prevents subtle bugs in a robot's rules.
Worked example
print(True or False and False)
print((True or False) and False)
a, b = True, False
print(not (a and b))
print((not a) or (not b))Parentheses change the meaning
Why: Without them, and binds tighter than or, giving True. With them, (True or False) and False is False.
De Morgan's law: a handy rewrite
Why: Verified by execution: not (a and b) equals (not a) or (not b) - both True here. Pushing a not across and/or flips the operator. Useful for simplifying tangled conditions.
| expression | value |
|---|---|
| True or False and False | True |
| (True or False) and False | False |
| not (a and b) | True |
| (not a) or (not b) | True |
Pattern
1. Name the conditions as yes/no questions
Why: Each branch tests something that is clearly True or False, like color == "red".
2. Order from most specific to least
Why: The first true branch wins, so the strictest test goes on top; the catch-all else goes last.
3. Use == to compare, never a single =
Why: One = assigns and is a SyntaxError inside an if. Two == ask whether values are equal.
4. Combine with and / or / not - full comparisons each side
Why: Write color == "red" or color == "green", never or "green". A bare value is always truthy.
5. Add an else as the safety net
Why: It catches every input you did not list - like an unknown color the robot still must handle.
Missing information
Discussion prompt
Read the color the robot sees and decide a move - the exact logic the camp robot uses.
What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.
Hint: Anything you would have to invent to get started is a thing the problem must supply.
Answer:
color is a string like "green". That is exactly what == compares against.
Worked example
Read the color the robot sees and decide a move - the exact logic the camp robot uses.
color = input("Color seen: ")
if color == "green":
print("forward")
elif color == "red":
print("turn")
else:
print("stop")Line 1: input() reads the color as text
Why: color is a string like "green". That is exactly what == compares against.
Lines 2-7: pick the move with if / elif / else
Why: green -> forward, red -> turn, and anything else -> stop. The else is the safety net for colors you did not list.
| the robot sees | branch | it prints |
|---|---|---|
| green | if | forward |
| red | elif | turn |
| blue / anything else | else | stop |
Estimation
Predict first
Run the same logic on four colors. Watch the else catch both blue and an unknown color.
Commit before you compute: what does Trace the brain on every color come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: One pass per color, one branch each
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Verified by execution. green and red hit the if and elif; blue and purple both fall to the else.
Worked example
Run the same logic on four colors. Watch the else catch both blue and an unknown color.
for color in ["green", "red", "blue", "purple"]:
if color == "green":
print("forward")
elif color == "red":
print("turn")
else:
print("stop")One pass per color, one branch each
Why: Verified by execution. green and red hit the if and elif; blue and purple both fall to the else.
| color | branch taken | prints |
|---|---|---|
| green | if | forward |
| red | elif | turn |
| blue | else | stop |
| purple | else | stop |
Discrimination
Sort into buckets
Sort these by branch taken, from memory, without looking back at Trace the brain on every color. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
A finite state machine (FSM) is a set of states plus rules for switching between them. At any moment the machine is in exactly one state.
state — One of the modes a machine can be in - like patrol or guard. A finite state machine has a fixed, countable list of them.
transition — A rule that switches the machine from one state to another when something happens - like 'seeing blue moves patrol to guard'.
Definition probe
Sort into buckets
Every line below is part of the definition of condition or of state — one or the other, never both. Put each where it belongs.
Intuition
Think of states as rooms in a building and transitions as doors. You are always standing in exactly one room, and certain events let you walk through a door into another.
What you can do depends on which room you are in. The same key (seeing blue) opens a different door depending on where you currently stand.
Concept
The point of an FSM: the same input produces a different action depending on the current state. Seeing red while patrolling means turn; seeing red while guarding means sound the alarm.
In code, the state is just a variable (like mode), and you check it with the same if / elif tools you already know.
Picture it
Figure (svg): Two circles labeled PATROL and GUARD, with arrows in both directions each labeled 'sees blue', showing the robot switches between the two states when it sees blue.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
Here is the robot's whole brain as a picture: two states, and a blue light flips between them.
Concept
Here is the robot's whole brain as a picture: two states, and a blue light flips between them.
Figure (svg): Two circles labeled PATROL and GUARD, with arrows in both directions each labeled 'sees blue', showing the robot switches between the two states when it sees blue.
Missing information
Discussion prompt
Red means turn while patrolling, but alarm while guarding. The state decides.
What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.
Hint: Anything you would have to invent to get started is a thing the problem must supply.
Answer:
Verified by execution: mode is "guard", so it takes the guard branch, sees red, and prints ALARM - not turn.
Worked example
Red means turn while patrolling, but alarm while guarding. The state decides.
mode = "guard"
color = "red"
if mode == "patrol":
if color == "red":
print("turn")
elif mode == "guard":
if color == "red":
print("ALARM")First check the state, then the color
Why: Verified by execution: mode is "guard", so it takes the guard branch, sees red, and prints ALARM - not turn.
| mode | color | action |
|---|---|---|
| patrol | red | turn |
| guard | red | ALARM |
Estimation
Predict first
Seeing blue doesn't just act - it changes the state, by reassigning the mode variable.
Commit before you compute: what does A transition: blue switches the mode come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Trace the state change
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Verified by execution: prints 'switching to guard' then 'mode is now guard'.
Worked example
Seeing blue doesn't just act - it changes the state, by reassigning the mode variable.
mode = "patrol"
color = "blue"
if mode == "patrol":
if color == "blue":
mode = "guard"
print("switching to guard")
elif mode == "guard":
if color == "blue":
mode = "patrol"
print("switching to patrol")
print("mode is now", mode)Line 5 reassigns the state variable
Why: In patrol, seeing blue runs mode = "guard" - that single assignment IS the transition.
Trace the state change
Why: Verified by execution: prints 'switching to guard' then 'mode is now guard'. The machine has moved rooms.
| step | mode |
|---|---|
| start | patrol |
| sees blue in patrol | guard |
| printed | mode is now guard |
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Trace the state change
What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.
Hint: Every quantity in the result had to enter somewhere. Account for each one.
Answer:
Seeing blue doesn't just act - it changes the state, by reassigning the mode variable.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Announcing the switch but never changing the mode variable.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: There is no mode = "guard" line, so mode is still "patrol".
Actually reassign the state variable to make the transition real.
Why: There is no mode = "guard" line, so mode is still "patrol". Next time, the robot behaves as if it never switched - a stuck machine.
Trap
Announcing the switch but never changing the mode variable.
mode = "patrol"
color = "blue"
if mode == "patrol" and color == "blue":
print("switching to guard")
print("mode is now", mode)It says it switched, but the state never moved
Why: There is no mode = "guard" line, so mode is still "patrol". Next time, the robot behaves as if it never switched - a stuck machine.
| expression | value |
|---|---|
| mode | patrol (unchanged!) |
Actually reassign the state variable to make the transition real.
mode = "patrol"
color = "blue"
if mode == "patrol" and color == "blue":
mode = "guard"
print("switching to guard")
print("mode is now", mode)mode = "guard" makes the switch stick
Why: Verified by execution: now mode is "guard" afterward. A transition is only real when the state variable actually changes.
| expression | value |
|---|---|
| mode | guard |
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
True or False (a bool). color == "red" asks: is color equal to red?; == checks for an exact match. "Red" is not equal to "red" - a capital letter is a different character.=.; Same grades, but the easiest condition is checked first.Concept
A two-state robot needs a handful of branches. A four-state robot reacting to five colors needs twenty - nested ifs become a wall of code that is hard to read and easy to break.
Two techniques scale far better: a dispatch dictionary for actions, and a transition table for the whole machine. Both turn logic into data.
Explain it
Discussion prompt
Explain When if/elif chains get big to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
A two-state robot needs a handful of branches. A four-state robot reacting to five colors needs twenty - nested ifs become a wall of code that is hard to read and easy to break.
Concept
Instead of an if/elif that maps each color to a move, store the map as a dictionary and look it up directly: actions[color].
dispatch table — A dictionary that maps each input to its action, replacing a long if/elif chain with a single lookup. Add a new input by adding a row, not a branch.
Matching
Match the pairs
Match each term to the definition this lesson gave it — not the one you would guess from the word.
Why: These are the working definitions of condition, state, transition, dispatch table as Decisions: if / elif / else & Finite-State Thinking uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Worked example
actions = {"green": "forward", "red": "turn", "blue": "stop"}
print(actions["red"])
print(actions.get("purple", "stop"))One lookup does the whole decision
Why: actions["red"] finds the key "red" and returns "turn" - no branching at all.
.get(key, default) handles unknown inputs
Why: Verified by execution: actions.get("purple", "stop") returns "stop" because purple is not a key. That default replaces the else branch.
| expression | value |
|---|---|
| actions["red"] | "turn" |
| actions["green"] | "forward" |
| actions.get("purple", "stop") | "stop" (default) |
Blank canvas
Draw it
Draw what Dispatch replaces if/elif just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Concept
A full FSM also needs the next state. Store the rules as a dictionary keyed by (state, input) whose value is (action, next_state).
Now the machine is data. Adding a state or a color is one new row in the table - not another branch buried inside nested ifs.
Analogy
Discussion prompt
Explain A transition table: the machine as data by analogy to something with no Python in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
A full FSM also needs the next state. Store the rules as a dictionary keyed by (state, input) whose value is (action, next_state).
Pattern
Predict first
The table runs: (patrol, blue) | (switch to guard, guard) · (guard, red) | (ALARM, guard)
In A table-driven FSM, given the rows so far: what is the next one — the row where (mode, color) is (guard, blue)?
Correct: (guard, blue) | (switch to patrol, patrol)
| (mode, color) | -> (action, next mode) |
|---|---|
| (patrol, blue) | (switch to guard, guard) |
| (guard, red) | (ALARM, guard) |
| (guard, blue) | (switch to patrol, patrol) |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. rules[("patrol", "blue")] is the pair ("switch to guard", "guard").
Worked example
rules = {
("patrol", "green"): ("forward", "patrol"),
("patrol", "red"): ("turn", "patrol"),
("patrol", "blue"): ("switch to guard", "guard"),
("guard", "green"): ("hold", "guard"),
("guard", "red"): ("ALARM", "guard"),
("guard", "blue"): ("switch to patrol", "patrol"),
}
mode = "patrol"
color = "blue"
action, mode = rules[(mode, color)]
print(action, "->", mode)One lookup gives BOTH the action and the next state
Why: rules[("patrol", "blue")] is the pair ("switch to guard", "guard"). Unpacking sets action and the new mode in a single line.
The transition is built into the table
Why: Verified by execution: prints switch to guard -> guard. The same row also makes mode become guard - no separate if needed. Every rule lives in one readable table.
| (mode, color) | -> (action, next mode) |
|---|---|
| (patrol, blue) | (switch to guard, guard) |
| (guard, red) | (ALARM, guard) |
| (guard, blue) | (switch to patrol, patrol) |
Error analysis
Annotate
Walk the callouts on A table-driven FSM. Each one is a place this is easy to get subtly wrong.
Concept
Nested if/elif is clearest for a small machine - two or three states and a few inputs. You can read the whole logic top to bottom.
A transition table wins as the machine grows: many states and inputs, rules that change often, or rules you load from a file. The camp robot starts with ifs and graduates to a table.
Counterexample
Discussion prompt
A transition table wins as the machine grows: many states and inputs, rules that change often, or rules you load from a file. The camp robot starts with ifs and graduates to a table.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Pattern
1. List the states
Why: Decide the fixed set of modes, e.g. patrol and guard. Store the current one in a variable like mode.
2. Branch on the state first
Why: Use if mode == ... to pick which set of rules applies right now.
3. Inside each state, decide on the input
Why: A second if/elif on the color (or sensor) chooses the action for that state.
4. Transition by reassigning the state
Why: When an event should switch modes, write mode = "...". Forgetting this leaves the machine stuck.
5. Keep states and inputs spelled exactly
Why: == is exact and case-sensitive, so "guard" must match "guard" everywhere - a typo silently breaks a branch.
Real world
Discussion prompt
Outside this lesson: where does Decisions: if / elif / else & Finite-State Thinking actually turn up? Name one concrete situation — a job, a piece of software someone ships, a decision somebody has to make — and say which part of How to build a finite state machine is doing the work in it.
Hint: Vague is the failure mode here. "Engineering" is not a situation; "deciding whether this build is fast enough to ship" is.
Answer:
Pre-COSMOS Day 6 (Cluster 10: Robot Inventors), 2+ hours in depth. Comparisons and booleans; if / elif / else with first-true-branch-wins so order matters; and / or / not (and the classic 'or green' bug); the == vs = trap; advanced techniques (the in operator, chained comparisons, truthiness, operator precedence and De Morgan's law); building the Maze Robot Brain v1; and finite state machines two ways - nested if/elif and table-driven (dictionary dispatch plus a (state, input) -> (action, next_state) transition table).
Check
Two comparisons - mind the capital letter.
print(3 == 3)
print("yes" == "Yes")| expression | value |
|---|---|
| 3 == 3 | ? |
| "yes" == "Yes" | ? |
Check your understanding
What does this print, line by line?
Answer: A
Why: == asks 'are these equal?' and returns a bool. 3 == 3 is True. "yes" == "Yes" is False because == is case-sensitive - lowercase y differs from uppercase Y. Verified by execution.
Trade off
Comparison matrix
From Check yourself: what does == return?: every row here is a choice with a cost. Fill the value column, then say which row you would actually pick and what you give up for it.
| expression | value |
|---|---|
| 3 == 3 | ? |
| "yes" == "Yes" | ? |
Check
Pick the single branch that runs.
color = "blue"
if color == "green":
print("forward")
elif color == "red":
print("turn")
else:
print("stop")| color | output |
|---|---|
| "blue" | ? |
Check your understanding
What does this print?
Answer: A
Why: color is "blue", which is not green and not red, so both the if and elif are False and the else branch runs, printing stop. Verified by execution.
Check
A 95 should be an A. What does this buggy code actually print?
score = 95
if score >= 70:
print("C")
elif score >= 80:
print("B")
elif score >= 90:
print("A")| score | output |
|---|---|
| 95 | ? |
Check your understanding
What does this print?
Answer: A
Why: The first true branch wins. 95 >= 70 is True, so it prints C and stops - even though 95 also passes the later tests. The fix is to check the strictest condition (>= 90) first. Verified by execution.
Check
Both parts of the and - decide the output.
temp = 40
if temp > 30 and temp < 50:
print("OK")
else:
print("out of range")| temp > 30 and temp < 50 | value |
|---|---|
| with temp = 40 | ? |
Check your understanding
What does this print?
Answer: A
Why: temp is 40. temp > 30 is True AND temp < 50 is True, so the combined condition is True and it prints OK. and needs both sides true. Verified by execution.
Check
color is "blue". Look closely at the or.
color = "blue"
if color == "red" or "green":
print("matched red or green")| the if | runs? |
|---|---|
| color == "red" or "green" | ? |
Check your understanding
Does this print "matched red or green"?
Answer: A
Why: Python reads this as (color == "red") or ("green"). The bare string "green" is always truthy, so the whole condition is always true - it prints even for blue. The fix is color == "red" or color == "green". Verified by execution.
Check
Trace the mode variable.
mode = "patrol"
color = "blue"
if mode == "patrol" and color == "blue":
mode = "guard"
print(mode)| step | mode |
|---|---|
| start | patrol |
| after the if | ? |
Check your understanding
What does this print?
Answer: A
Why: mode starts as "patrol" and color is "blue", so the and condition is true and mode is reassigned to "guard". print(mode) then shows guard. Reassigning the state variable is what makes a finite state machine change states. Verified by execution.
Comparison
Comparison matrix
From Check yourself: the state transition: refill the mode column from what you know. The rest of the table is as it appeared.
| step | mode |
|---|---|
| start | patrol |
| after the if | ? |
Check
Is the color one of the listed three?
color = "yellow"
print(color in ["red", "green", "blue"])| expression | value |
|---|---|
| color in [...] | ? |
Check your understanding
What does this print?
Answer: A
Why: in checks whether color is one of the items in the list. "yellow" is not in ["red", "green", "blue"], so it returns False. Verified by execution.
Check
Trace the unpacking on line 2.
rules = {("patrol", "blue"): ("switch", "guard")}
action, mode = rules[("patrol", "blue")]
print(mode)| variable | value |
|---|---|
| mode | ? |
Check your understanding
What does this print?
Answer: A
Why: rules[("patrol", "blue")] is the pair ("switch", "guard"). Unpacking sets action to "switch" and mode to "guard", so print(mode) shows guard. Verified by execution.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — How to write a decision · How to build a finite state machine · Programs make decisions · A condition is a yes/no question · Comparison operators. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
Conditions return True/False; if / elif / else runs the first true branch (so order matters); and / or / not combine tests; and a finite state machine is states plus transitions you make by reassigning a mode variable.
| You want | Write | Remember |
|---|---|---|
| compare values | color == "red" | two ==, not one = |
| choose a path | if ... elif ... else | first true branch wins |
| both true | a and b | or = at least one |
| one of many | color in ["red", "green"] | clean, and dodges the or bug |
| WRONG or | color == "red" or "green" | always true - a bug |
| switch state | mode = "guard" | a transition reassigns the state |
| a bigger machine | rules[(mode, color)] | table-driven FSM scales |
The big ideas: order your branches most-specific first, give each or side a full comparison, and make a state change real by reassigning the state variable. Now go build the Maze Robot Brain - and give it patrol and guard modes.
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.