DSM
60 XP
35 minsBeginner60 XP

Dictionaries

Finding one customer in a list of a million records means checking rows until you hit it. Finding them in a dictionary means asking by name — instantly, whether there are a hundred entries or a hundred million. That lookup-by-name superpower is why dicts are everywhere: configs, JSON, API responses, pandas' internals.

What you'll learn

  • Create, read, update, and delete dict entries
  • Handle missing keys safely with in, .get(), and defaults
  • Iterate keys, values, and .items() pairs
  • Build the counting and grouping patterns from scratch
  • Recognize dicts as the Python face of JSON

What

A dictionary maps KEYS to VALUES: {'name': 'Ada', 'plan': 'pro'}. You store by key, retrieve by key, and update by key — no positions, no scanning. Keys must be immutable (strings, numbers, tuples); values can be anything.

Why

Data IS key→value at every scale: a user profile, a config file, a JSON API response, a row with named columns, a count per category. Fluency with dicts — including .get(), .items(), and the counting pattern — is the single highest-leverage Python skill for data work.

Where it's used

JSON payloads, configuration, counting/grouping (the heart of analytics), lookup tables, function kwargs, and pandas' column-oriented core.

Where this runs in production

StripeEvery API response

A Stripe charge arrives as JSON — in Python, a dict: charge['amount'], charge['currency'], charge['metadata']['order_id']. Payment integrations are dict navigation.

NetflixFeature flags & config

Service configuration ships as nested dicts — flags, limits, rollout percentages — read by key at startup. A typo'd key raising KeyError loudly beats a silent wrong default.

spaCyWord counts at NLP scale

Vocabulary building is the counting pattern: one pass over tokens, counts[word] = counts.get(word, 0) + 1 — the exact loop you'll write today, industrialized.

Theory

The core ideas, in plain language.

Create with braces and colons: user = {'name': 'Ada', 'logins': 42}. Read with square brackets by KEY: user['name']. Assign to add or overwrite: user['plan'] = 'pro' inserts a new pair; user['logins'] = 43 replaces. Delete with del user['plan']. len() counts pairs; `in` tests KEYS.
user = {'name': 'Ada', 'logins': 42}
print(user['name'])       # read by key
user['plan'] = 'pro'      # insert
user['logins'] += 1       # update in place
print(user)
print('plan' in user)     # membership tests KEYS

The bracket syntax looks like list indexing, but the meaning is different: user['name'] is a lookup by NAME, not position. Dicts preserve insertion order (guaranteed since Python 3.7), but you should think in keys, not positions.

Analogy: The coat check

A dictionary is a coat check. You hand over a coat (value) and get a numbered ticket (key). Retrieval is instant: present ticket 47, receive that exact coat — the attendant doesn't rummage through every rack (that's the list-scan you're escaping). Two coats can be identical, but tickets are unique: hand in a second coat under ticket 47 and it REPLACES the first. And a made-up ticket gets you an apologetic shrug — KeyError.

Key Concept
Missing keys: pick your policy

user['age'] on an absent key raises KeyError — loud, immediate, often exactly right (a missing config key SHOULD crash at startup). When absence is normal, choose deliberately: `if 'age' in user:` to test first, or user.get('age') for None, or user.get('age', 0) for a fallback. The counting pattern is built on that third form. Rule of thumb: [] when the key MUST exist, .get() when it may not.

votes = ['py', 'sql', 'py', 'r', 'py', 'sql']
counts = {}
for lang in votes:
    counts[lang] = counts.get(lang, 0) + 1
print(counts)

THE canonical dict pattern: count occurrences in one pass. .get(lang, 0) reads the current tally or 0 for first-timers; the assignment writes it back plus one. Every groupby you'll ever run is this loop wearing a suit.

Iteration comes in three flavors: `for key in d:` walks keys; `for v in d.values():` walks values; `for k, v in d.items():` walks (key, value) TUPLES — unpacked in the header, exactly the skill from last lesson. .items() is the one you'll use most: nearly every dict loop wants both halves.
Watch out
JSON ≈ dicts, and two sharp edges

Every JSON object you'll ever receive becomes a Python dict (json.loads) and back (json.dumps) — master dicts and you've mastered API data. Two edges to respect: (1) assigning to an existing key silently OVERWRITES — building a lookup from data with duplicate keys keeps only the last one, a classic silent data loss; (2) `for k in d: del d[k]` — mutating a dict while iterating it — raises RuntimeError; collect keys first or build a new dict.

Visual Learning

See the concept, then explore it.

What happens on counts['py'] — hit vs miss

One lookup, two possible outcomes, three policies for the miss. Click each node.

Worked Examples

Watch it built up, one line at a time.

Very EasyCreate, read, update

A product record with named fields.

Step 1 of 2

Read by key name — no remembering that price is 'position 1'. Names beat positions for anything with more than two fields.

Code
01product = {'sku': 'KB-201', 'price': 49.99, 'stock': 12}
02print(product['price'])
Practice Coding

Your turn — write the code.

Your task

Run a small warehouse ledger. Starting from an empty dict, process a list of ('item', qty) movements (negative = shipped out). Build the stock levels with .get(), then print each item and its final level, plus a LOW STOCK warning list for anything below 5.

Expected output
bolt: 10
nut: 3
washer: 3
LOW STOCK: ['nut', 'washer']

Write your solution in the editor on the right, then hit Run.

Exercises

Prove it. Reach 80% to complete the lesson.

Mastery Gate0% / 80% required
Easy0/2 solved

d = {'a': 1}; print(d['b']) — what happens?

Which CANNOT be a dictionary key?

Medium0/3 solved

counts = {}; then for each word: counts[word] = counts.get(word, 0) + 1. What does .get(word, 0) do for a word seen before?

ScenarioYou build a lookup from a CSV: for row in rows: emails[row_name] = row_email. The file has 1,048 rows but len(emails) is 973, and no error was raised.

What most likely happened?

grades = {'mia': 88, 'kai': 61, 'ada': 95}. Print each student as '<name>: <grade>' (insertion order), then print the name of the top scorer using max with a key. Expected: mia: 88 kai: 61 ada: 95 top: ada

Iterates items():The loop unpacks (name, grade) pairs from .items()
max over keys by value:max(grades, key=grades.get) — iterating a dict yields keys; the key function looks up each one's value
Hard0/2 solved

Why is looking up one key in a dict of 10 million entries about as fast as in a dict of 10?

orders = [('mia', 'espresso'), ('kai', 'latte'), ('mia', 'latte'), ('ada', 'espresso'), ('mia', 'mocha')]. Group into a dict of customer → list of drinks, then print each customer as '<name> ordered <n>: <drinks joined by, >'. Expected: mia ordered 3: espresso, latte, mocha kai ordered 1: latte ada ordered 1: espresso

Grouping pattern:Ensures an empty list per new key, then appends
Joined output:', '.join(drinks) renders each customer's list
Complete 80% more exercises to unlock.
Interview Prep

How this shows up in real interviews.

Why are dictionary lookups so fast, and what constraint on keys does that speed impose?

Show model answer

Dicts are hash tables: inserting a key hashes it — computes an integer from its value — which determines the bucket where the entry is stored. Lookup repeats the hash and jumps straight to that bucket, so the cost is essentially constant whether the dict holds ten entries or ten million, versus a list scan that grows linearly. The constraint follows directly: keys must be HASHABLE, meaning their hash can never change, which effectively means immutable — strings, numbers, tuples of immutables. A mutable key like a list would leave its entry stranded in a stale bucket the moment it changed, so Python rejects it up front with TypeError: unhashable. That's also the practical answer to 'why tuples as composite keys': ('store-12', '2026-07-14') is frozen, so it hashes reliably forever.

Compare the three ways to handle a possibly-missing key. When is each right?

Show model answer

Bracket access — d[k] — raises KeyError on a miss: right when the key is REQUIRED, like a config value or schema field, because failing loudly at the source beats propagating a silent default into downstream computations. .get(k) returns None quietly: right when absence is a normal state and the caller will branch on it, tested with `is not None` when falsy values are legitimate. .get(k, default) returns a fallback: right for accumulation and display — counts.get(word, 0) + 1 is the canonical counting pattern, initializing first-timers and incrementing veterans in one expression. The interview-grade point: this is a POLICY decision about your data, made explicit in syntax — reaching for .get everywhere 'to be safe' actually hides real bugs that brackets would have caught on day one.

Sketch how you'd group a list of records by a field using plain dicts, and relate it to groupby.

Show model answer

One pass with a dict of lists: create an empty dict; for each record, extract the group key; if the key isn't present, initialize it with an empty list (or use setdefault(key, []) to do both steps at once); append the record. Afterwards, iterate .items() and reduce each bucket — sum, mean, count — into your result. That two-phase shape, partition-then-aggregate, is exactly what df.groupby('region')['amount'].mean() performs: pandas partitions rows into per-key buckets and applies the reduction per bucket, just vectorized and optimized. Knowing the manual version matters for three reasons: it works when data isn't tabular (nested JSON, streaming events), it demystifies groupby's semantics — including why the group key becomes the index — and it's a standard interview warm-up where the counting variant (values as ints) and grouping variant (values as lists) show you understand that a dict's value can be any accumulator you choose.

Common Mistakes to Avoid

1) d[key] on optional data — KeyError; choose your missing-key policy deliberately. 2) Building lookups from data with duplicate keys — later rows silently overwrite earlier ones; count both sides. 3) Using a list as a key — unhashable TypeError; a tuple works. 4) Mutating a dict while iterating it — RuntimeError; iterate a copy of the keys or build a new dict. 5) Expecting `in` to test values — it tests KEYS; use `in d.values()` explicitly. 6) .get() everywhere 'for safety' — required keys deserve the loud failure of brackets.

Ask the AI Tutor

Try these prompts in the AI Tutor panel: • 'Quiz me: bracket, .get(), or in — which policy fits each of these six situations?' • 'Walk me through the counting pattern on a fresh dataset.' • 'Show a nested JSON payload and drill me on extracting fields.' • 'Explain hashing with a diagram-in-words.' • 'Interview mode: ask me to group records by a field and grade my code.'

Glossary

Dictionary (dict) — a mutable mapping of unique keys to values. Key — the immutable lookup handle; hashed for instant access. Value — the data stored under a key; any type. KeyError — raised by d[k] when k is absent. .get(k, default) — lookup with a fallback, never raises. .items()/.keys()/.values() — iteration views. Hashable — has a stable hash; required for keys. Counting pattern — d[k] = d.get(k, 0) + 1. Grouping pattern — dict of lists, append per key. setdefault(k, v) — get k's value, inserting v first if absent. JSON — the text format whose objects map to dicts.

Recommended Resources

• Docs: 'Dictionaries' in the official Python tutorial and the dict methods reference. • Read: collections.Counter docs — the standard library's professional counting pattern (Counter(votes).most_common(3)). • Practice: take any API's example JSON response and extract three nested fields, choosing bracket vs .get() deliberately for each. • Next in DSM: dicts give you unique KEYS — Sets take just that uniqueness and make it a structure of its own: membership, dedup, and set algebra.

Recap

✓ Dicts map immutable keys to any values; brackets read/insert/overwrite by key. ✓ Lookup is hash-powered: constant-time at any size — think in keys, not positions. ✓ Missing keys are a policy: [] loud, .get() quiet, .get(k, default) self-healing. ✓ The counting pattern (get(k, 0) + 1) and grouping pattern (dict of lists) power all analytics. ✓ .items() + unpacking is how dict loops are written. ✓ JSON objects ARE dicts in Python — API work is dict navigation. Next up: Sets. Strip a dict down to just its unique keys and you get the set — instant membership tests, one-line deduplication, and the union/intersection algebra of comparing groups.

scratchpad — preview this lesson's challenge anytime
inventory.pyPython
Ready
Output

Run your code to see the output here.