Pre-COSMOS Day 12, for Cluster 10: Robot Inventors - an advanced session of 50 slides. Dictionaries are the natural way to map a color to an action, or a state to what comes next, which is exactly how the camp's finite-state robot logic works, and the diagnostic rated this fragile. The session covers key-to-value lookup, dictionaries against lists, and the KeyError trap, since keys are not numeric positions - it is actions["red"], not actions[0]. It then covers safe lookups with .get(key, default) and the in operator, adding and updating keys, len, and looping with keys(), values(), and items(), before two advanced patterns: the counter or tally, and the dictionary as a finite state machine. It builds to a fully scaffolded your-turn Cheat-Code Console - a codes dictionary, input, a .get default, and a while-quit loop - with a usage-tally stretch. There are five checks, three traps, two photos, and SVG diagrams of the lookup table and the FSM. Every snippet was run in real Python, covering the KeyErrors, .get, the tally, and the FSM transitions, with the output copied verbatim into the trace tables.
Subject: Python · 80 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Day 12 · Cluster 10: Robot Inventors
The natural way to map a color to an action - or a state to what comes next. This is how the camp's finite-state robot logic works.
Objectives
A dict lets the robot answer "when I see X, do Y" instantly. The diagnostic rated this fragile - by the end you'll have it solid. You can:
actions["red"] - not by number position..get(key, default) for a lookup that won't crash on a missing key.len, and check with in.keys(), values(), and items().Warm-up
Discussion prompt
Before we open Dictionaries: Mapping Colors to Actions: without looking back, what was the main idea of Color & Seeing: Detecting a Color is a Mask, 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:
Pre-COSMOS Lesson 5 of 8 (41 slides): how a computer 'sees' a color. A pixel is three numbers [R,G,B], each 0..255; a color image is a grid of pixels with shape (H, W, 3).
Concept

A robot constantly maps one thing to another: a color to an action, a state to the next state, a command to a response.
A dictionary is exactly that map - ask it by name and it hands back the answer, with no scanning through positions.
It was fragile on the diagnostic for one reason: people treated it like a list. Today we fix that for good.
Counterexample
Discussion prompt
A robot constantly maps one thing to another: a color to an action, a state to the next state, a command to a response.
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 dictionary is exactly that map - ask it by name and it hands back the answer, with no scanning through positions.
Concept
Four ideas, then you build a console out of them:
actions["red"] - ask by name..get(key, default) never crashes.Matching
Match the pairs
From Today's roadmap — match each one to what it actually does. The descriptions have been shuffled.
actions["red"] - ask by name..get(key, default) never crashes.Why: Look up by key, Safe lookups, Loop & patterns, Cheat-Code Console are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.
Section
Section 1
Concept
A dictionary stores key → value pairs in curly braces. You look something up by its key, and it hands back that key's value.
dictionary — A collection of key:value pairs, written {"red": "turn"}. Each key is unique and is how you find its value - there are no number positions.
Analogy
Discussion prompt
Explain A dictionary maps keys to values 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 dictionary stores key → value pairs in curly braces. You look something up by its key, and it hands back that key's value.
Concept
A list finds things by position; a dictionary finds them by name. That's the whole difference - and the source of every dict bug.
| list | dictionary | |
|---|---|---|
| access by | position: 0, 1, 2 | key: "red" |
| example | colors[0] | actions["red"] |
| order is how you find it | yes | no - you ask by name |
| best for | a sequence | a mapping / lookup |
Comparison
Comparison matrix
From Dictionary vs list: refill the list column from what you know. The rest of the table is as it appeared.
| list | dictionary | |
|---|---|---|
| access by | position: 0, 1, 2 | key: "red" |
| example | colors[0] | actions["red"] |
| order is how you find it | yes | no - you ask by name |
| best for | a sequence | a mapping / lookup |
Picture it
Figure (svg): A two-row lookup table: red points to turn, green points to go
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:
Picture a little table: each key on the left points to its value on the right. You don't count to it - you read across from the label.
Intuition
Picture a little table: each key on the left points to its value on the right. You don't count to it - you read across from the label.
Figure (svg): A two-row lookup table: red points to turn, green points to go
Explain it
Discussion prompt
Explain A dict is a labeled lookup table 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:
Picture a little table: each key on the left points to its value on the right. You don't count to it - you read across from the label.
Worked example
Build the map once, then ask it by key.
actions = {"red": "turn", "green": "go"}
print(actions["red"])
print(actions["green"])actions["red"] reads across from the key "red" to its value.
| lookup | value |
|---|---|
| actions["red"] | turn |
| actions["green"] | go |
Trade off
Comparison matrix
From Looking up an action: 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.
| lookup | value |
|---|---|
| actions["red"] | turn |
| actions["green"] | go |
Concept
This is the big one. A dictionary has no positions. actions[0] does not mean "the first pair" - it looks for a key called 0, and there isn't one.
Always look up by the real key: actions["red"], never actions[0].
Anomaly
Predict first
A student writes this, and it looks reasonable:
Reaching for the "first item" by position.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: There's no key 0 in the dict - dicts don't have number positions.
Look it up by its key.
Why: There's no key 0 in the dict - dicts don't have number positions.
Trap
Reaching for the "first item" by position.
Write actions[0]
Why: There's no key 0 in the dict - dicts don't have number positions.
Python raises KeyError: 0
Why: [ ] on a dict means "find this key", and key 0 doesn't exist.
Look it up by its key.
Write actions["red"]
Why: "red" is a real key, so the dict hands back its value.
Result: "turn"
Why: Keys are names, not positions - ask by the name you stored.
Ranking
Put in order
These are the steps of How to look something up, scrambled. Put them back in order before the next slide shows you.
d[key].d.get(key, default) so it can't crash.key in d.Why: This is the order the recipe itself gives. Recalling the sequence without the slide in front of you is the difference between recognising the method and being able to run it — most of what goes wrong in practice is a step done out of turn.
Pattern
Every dictionary lookup you write follows this:
d[key].d.get(key, default) so it can't crash.key in d.Edge cases
Discussion prompt
How to look something up works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
Every dictionary lookup you write follows this:
Check
Recall actions = {"red": "turn", "green": "go"}.
Check your understanding
What does actions[0] do?
Answer: A
Why: Dictionaries look things up by key. There is no key 0 in this dict, so actions[0] raises KeyError: 0. Use actions["red"].
Section
Section 2
Concept
actions["blue"] on a missing key crashes with KeyError. actions.get("blue") returns None instead, and actions.get("blue", "wait") returns a default you choose.
.get(key, default) — Looks up key; if it's missing, returns default (or None if you give no default) instead of raising KeyError.
Worked example
Same dict, three lookups - watch the missing key.
actions = {"red": "turn", "green": "go"}
print(actions.get("red"))
print(actions.get("blue"))
print(actions.get("blue", "wait"))"blue" isn't a key, so the default kicks in - no crash.
| lookup | result |
|---|---|
| actions.get("red") | turn |
| actions.get("blue") | None |
| actions.get("blue", "wait") | wait |
Concept
key in d is True when the dictionary has that key. Handy before a d[key] lookup, or to branch on whether something is known.
in — color in actions tests whether color is a KEY of the dict (not a value). Returns True or False.
Worked example
in checks keys, returning a simple True/False.
actions = {"red": "turn", "green": "go"}
print("red" in actions)
print("blue" in actions)"red" is a key (True); "blue" is not (False).
| test | result |
|---|---|
| "red" in actions | True |
| "blue" in actions | False |
Anomaly
Predict first
A student writes this, and it looks reasonable:
Looking up player input directly with square brackets.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: If the key isn't there, the whole program crashes with KeyError.
Use .get with a safe default for anything that might miss.
Why: If the key isn't there, the whole program crashes with KeyError.
Trap
Looking up player input directly with square brackets.
actions[player_color] when input is "blue"
Why: If the key isn't there, the whole program crashes with KeyError.
One bad input = a crash
Why: Anything a user types could be a key you never stored.
Use .get with a safe default for anything that might miss.
actions.get(player_color, "wait")
Why: A missing key returns "wait" instead of crashing.
Bad input = a graceful default
Why: Use [ ] only for keys you're certain exist.
Break the constraint
Discussion prompt
The rule this trap just fixed:
A missing key returns "wait" instead of crashing.
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:
If the key isn't there, the whole program crashes with KeyError.
Elimination
Eliminate the wrong options
What does actions.get("blue", "wait") return?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: "blue" isn't a key, so .get returns the default you supplied: "wait".
Check
Recall actions = {"red": "turn", "green": "go"}.
Check your understanding
What does actions.get("blue", "wait") return?
Answer: A
Why: "blue" isn't a key, so .get returns the default you supplied: "wait".
Section
Section 3
Concept
d[key] = value does double duty: if the key is new, it's added; if it already exists, its value is overwritten.
Keys are unique - a dict can't hold "red" twice, so a second assignment replaces the first.
Socratic
Discussion prompt
d[key] = value does double duty: if the key is new, it's added; if it already exists, its value is overwritten.
Suppose that were not true. What is the first thing in Dictionaries: Mapping Colors to Actions that would stop working?
Hint: Follow it one step downstream. The answer is whatever was quietly relying on it.
Answer:
Keys are unique - a dict can't hold "red" twice, so a second assignment replaces the first.
Worked example
Add "yellow" (new key), then change "red" (existing key).
actions = {"red": "turn", "green": "go"}
actions["yellow"] = "slow"
actions["red"] = "stop"
print(actions)
print(len(actions))"yellow" is added; "red" is overwritten, not duplicated.
| after line | dict | len |
|---|---|---|
| start | red:turn, green:go | 2 |
| actions["yellow"]="slow" | ...yellow:slow added | 3 |
| actions["red"]="stop" | red now "stop" | 3 |
Concept
len(d) is the number of keys (which equals the number of pairs). Overwriting a key doesn't change it - only adding a new key does.
Sorting
Sort into buckets
These are the pieces of Dictionaries: Mapping Colors to Actions, out of order. Put each one back under the part of the lesson it belongs to.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Expecting len to grow every time you assign.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: It looks like adding, so it feels like the count should rise.
Assigning an existing key updates its value in place.
Why: It looks like adding, so it feels like the count should rise.
Trap
Expecting len to grow every time you assign.
actions["red"] = "stop" when "red" exists
Why: It looks like adding, so it feels like the count should rise.
Expect len 2 → 3
Why: But "red" was already a key, so nothing new was added.
Assigning an existing key updates its value in place.
actions["red"] = "stop"
Why: "red"'s value changes from "turn" to "stop".
len stays 2
Why: Only a brand-new key raises the count.
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.
Check
Start from {"red": "turn", "green": "go"}.
Check your understanding
d = {"red": "turn", "green": "go"}
d["red"] = "stop"
print(len(d))
Answer: A
Why: "red" already exists, so d["red"]="stop" overwrites its value - no new key. len stays 2.
Section
Section 4
Concept
A plain for k in d: walks the keys. From each key you can reach its value with d[k].
Worked example
Each pass gives a key; actions[k] gets its value.
actions = {"red": "turn", "green": "go"}
for k in actions:
print(k, actions[k])k is the key each pass - never a number.
| pass | k | prints |
|---|---|---|
| 1 | red | red turn |
| 2 | green | green go |
Concept
d.items() hands you both at once. Unpack them into two loop variables: for k, v in d.items():.
.items() — Yields each (key, value) pair, so for k, v in d.items() gives you both without a second lookup.
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 dictionary, .get(key, default), in, .items() as Dictionaries: Mapping Colors to Actions uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Worked example
No actions[k] needed - you already have the value.
actions = {"red": "turn", "green": "go"}
for k, v in actions.items():
print(k, "->", v)k, v unpack each pair together.
| pass | k | v |
|---|---|---|
| 1 | red | turn |
| 2 | green | go |
Concept
d.keys() gives just the keys; d.values() gives just the values. Wrap in list(...) to see them as a list.
d = {"red": "turn", "green": "go"}
print(list(d.keys()))
print(list(d.values()))| call | result |
|---|---|
| list(d.keys()) | ['red', 'green'] |
| list(d.values()) | ['turn', 'go'] |
Comparison
Comparison matrix
From keys() and values(): refill the result column from what you know. The rest of the table is as it appeared.
| call | result |
|---|---|
| list(d.keys()) | ['red', 'green'] |
| list(d.values()) | ['turn', 'go'] |
Intuition
for k in d.for k, v in d.items() - cleanest.for v in d.values().Reach for .items() by default when you'll use the value - it saves a lookup and reads clearly.
Prediction
Predict first
d = {"red": "turn", "green": "go"} for k, v in d.items(): print(k, v)
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: red turn / green go
Why: .items() yields (key, value) pairs, so k, v = 'red','turn' then 'green','go'. Each line prints the key then its value.
Check
Predict the two printed lines.
Check your understanding
d = {"red": "turn", "green": "go"}
for k, v in d.items():
print(k, v)
Answer: A
Why: .items() yields (key, value) pairs, so k, v = 'red','turn' then 'green','go'. Each line prints the key then its value.
Section
Section 5
Concept
To tally how often things appear, use the dict as a scoreboard: counts[item] = counts.get(item, 0) + 1.
The .get(item, 0) gives 0 the first time an item shows up, so you never crash on a key that isn't there yet.
Worked example
Start empty; each color bumps its own count.
counts = {}
for color in ["red", "red", "green", "blue"]:
counts[color] = counts.get(color, 0) + 1
print(counts).get(color, 0) returns 0 on first sight, then the running total.
| sees | counts.get(color,0)+1 | counts now |
|---|---|---|
| red | 0 + 1 | {red: 1} |
| red | 1 + 1 | {red: 2} |
| green | 0 + 1 | {red: 2, green: 1} |
| blue | 0 + 1 | {red: 2, green: 1, blue: 1} |
Concept
Map each state to the next state. The robot's whole behavior loop becomes one dictionary lookup per step - this is finite-state logic.
Figure (svg): Three states forward, scan, turn in a cycle with arrows forward to scan to turn back to forward
Explain it
Discussion prompt
Explain Pattern 2: a dict as a state machine 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:
Map each state to the next state. The robot's whole behavior loop becomes one dictionary lookup per step - this is finite-state logic.
Worked example

One dict drives the whole cycle; the loop just follows it.
next_state = {"forward": "scan",
"scan": "turn",
"turn": "forward"}
state = "forward"
for _ in range(4):
print(state, "->", next_state[state])
state = next_state[state]next_state[state] is the lookup; reassigning state advances it.
| step | state | next_state[state] |
|---|---|---|
| 1 | forward | scan |
| 2 | scan | turn |
| 3 | turn | forward |
| 4 | forward | scan |
Pattern
Step through it
Step through The state machine, looping one row at a time. What is driving the change, and what would the row after the last one be?
Concept
Keys are usually strings (a color, a code, a state) or numbers - something stable you'll ask for by name. Each key is unique; the value can be anything.
Rule of thumb: if you find yourself asking "what's the X for this Y?", Y is your key and X is your value.
Analogy
Discussion prompt
Explain What makes a good key 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:
Keys are usually strings (a color, a code, a state) or numbers - something stable you'll ask for by name. Each key is unique; the value can be anything.
Elimination
Eliminate the wrong options
counts = {} for c in ["red", "red", "green"]: counts[c] = counts.get(c, 0) + 1 print(counts)
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: .get(c, 0) returns 0 the first time and the running count after, so the two reds make red 2 and green is 1.
Check
Trace the tally to the end.
Check your understanding
counts = {}
for c in ["red", "red", "green"]:
counts[c] = counts.get(c, 0) + 1
print(counts)
Answer: A
Why: .get(c, 0) returns 0 the first time and the running count after, so the two reds make red 2 and green is 1.
Section
Section 6 · Your turn
Concept
A dict maps secret codes to power-ups. The program reads what the player types and answers with .get - friendly default for codes it doesn't know. Type every line, run as you go, read errors - don't erase them.
| # | piece | tool |
|---|---|---|
| 1 | the codes map | a dict {code: power-up} |
| 2 | read + respond | input() then .get(typed, default) |
| 3 | keep going | a while loop until "quit" |
Worked example
Your turn: make a dict codes mapping at least two secret codes to power-ups. Predict codes["UUDD"].
Hint: codes = {"UUDD": "extra life", "HESOYAM": "full health"}.
codes = {"UUDD": "extra life",
"HESOYAM": "full health"}
print(codes["UUDD"])| lookup | value |
|---|---|
| codes["UUDD"] | extra life |
| codes["HESOYAM"] | full health |
Worked example
Your turn: read what the player types and answer with .get and a friendly default. Why .get and not [ ]?
Hint: typed = input("Enter code: ") then print(codes.get(typed, "unknown code")).
typed = input("Enter code: ")
print(codes.get(typed, "unknown code"))| player types | output |
|---|---|
| UUDD | extra life |
| HESOYAM | full health |
| XYZ | unknown code |
Pattern
Step through it
Step through Step 2 — read a code, respond safely one row at a time. What is driving the change, and what would the row after the last one be?
Worked example
Your turn: wrap it in a while loop so the player can keep entering codes until they type quit.
Hint: while True: ... if typed == "quit": break before the lookup.
while True:
typed = input("Enter code: ")
if typed == "quit":
print("bye")
break
print(codes.get(typed, "unknown code"))| player types | output |
|---|---|
| UUDD | extra life |
| ZZZ | unknown code |
| quit | bye (loop ends) |
Trade off
Comparison matrix
From Step 3 — keep asking until "quit": every row here is a choice with a cost. Fill the output column, then say which row you would actually pick and what you give up for it.
| player types | output |
|---|---|
| UUDD | extra life |
| ZZZ | unknown code |
| quit | bye (loop ends) |
Worked example
The whole program - a map, a loop, and one safe lookup.
codes = {"UUDD": "extra life",
"HESOYAM": "full health"}
while True:
typed = input("Enter code: ")
if typed == "quit":
print("bye")
break
print(codes.get(typed, "unknown code")).get keeps a wrong code from ever crashing the console.
| session | console says |
|---|---|
| UUDD | extra life |
| hello | unknown code |
| HESOYAM | full health |
| quit | bye |
If yours answers known codes, shrugs off unknown ones, and quits cleanly - you built it.
Comparison
Comparison matrix
From Put it together: the console: refill the console says column from what you know. The rest of the table is as it appeared.
| session | console says |
|---|---|
| UUDD | extra life |
| hello | unknown code |
| HESOYAM | full health |
| quit | bye |
Worked example
Stretch: add a second dict that tallies uses - the counting pattern from earlier, inside your loop.
Hint: uses[typed] = uses.get(typed, 0) + 1 before the .get response; print uses when they quit.
uses = {}
# inside the loop, before responding:
uses[typed] = uses.get(typed, 0) + 1
# after the loop:
print(uses)| typed this session | uses at quit |
|---|---|
| UUDD, UUDD, ZZZ | {'UUDD': 2, 'ZZZ': 1} |
Concept
Explain your console in dictionary terms:
codes..get instead of codes[typed]?"unknown code" protect against?If you can answer all four, dictionaries are no longer fragile - they're your go-to tool.
Counterexample
Discussion prompt
If you can answer all four, dictionaries are no longer fragile - they're your go-to tool.
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.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — What a Dictionary Is · Safe Lookups with .get · Change & Measure · Looping Over a Dictionary · Two Pro Patterns · Build It: Cheat-Code Console. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
actions["red"]..get(key, default) and key in d for lookups that might miss.d[key] = value; len counts keys..items(), and use a tally and a state-machine dict.| task | the move |
|---|---|
| look up a value | actions["red"] |
| safe lookup | actions.get(c, "wait") |
| is the key there? | c in actions |
| add / update | actions[c] = action |
| key + value loop | for k, v in d.items() |
| count things | d[x] = d.get(x, 0) + 1 |
That's the data structure behind the robot's whole decision table - mapping what it senses to what it does.
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.