Code of the Day
IntermediateThinking About Problems

Key-value thinking

How hash maps (dicts) enable O(1) lookup, and two patterns — counting and grouping — that solve a surprising range of problems.

CS FundamentalsIntermediate7 min read
By the end of this lesson you will be able to:
  • Explain what a hash map (dict) is and why lookup is O(1)
  • Describe when to trade memory for speed using a dict
  • Identify the "count with a dict" and "group with a dict" patterns

A list is an ordered sequence. To find something in a list, you scan from the start — O(n). A (also called a hash map or hash table) is a different that maps keys to values with effectively O(1) lookup: the time to find a key doesn't grow with the number of entries.

Why dict lookup is O(1)

When you write d["alice"] = 42, Python doesn't store the entry at the end of a list to be scanned later. It runs a hash function on "alice" to produce a number, uses that number to compute a storage slot, and puts the value there. When you later ask for d["alice"], it hashes "alice" again, goes straight to the same slot, and reads the value. One hop, regardless of how many keys are in the dict.

The detail that matters for problem-solving: if you find yourself scanning a list repeatedly for the same information, a dict can often reduce that to a single scan that builds the dict, followed by O(1) lookups.

Pattern 1: Count with a dict

Problem. Given a list of words, how many times does each word appear?

Brute force: for each unique word, scan the whole list and count — O(n²). With a dict: make a single pass, incrementing a counter for each word — O(n).

words = ["cat", "dog", "cat", "bird", "dog", "cat"]
counts = {}
for word in words:
    if word in counts:
        counts[word] += 1
    else:
        counts[word] = 1
# counts == {"cat": 3, "dog": 2, "bird": 1}

The dict key is the thing you're counting; the value is the count. You'll use this pattern constantly.

Pattern 2: Group with a dict

Problem. Given a list of words, group them by their length.

One pass: for each word, use its length as the key and append to a list of words at that key.

words = ["cat", "dog", "elephant", "ant", "bee", "ox"]
by_length = {}
for word in words:
    length = len(word)
    if length not in by_length:
        by_length[length] = []
    by_length[length].append(word)
# by_length == {3: ["cat", "dog", "ant", "bee"], 8: ["elephant"], 2: ["ox"]}

The dict key is the grouping criterion; the value is a list of items in that group. You can use any hashable value as a key — lengths, first letters, categories, whatever makes the grouping meaningful.

When to reach for a dict

The mental trigger: "I'm scanning a list more than once for the same thing."

If you scan once to build a set of values and then check membership repeatedly, convert it to a set (O(1) membership test, like a dict without values). If you need to store associated data, use a dict. Either way you're trading memory — the dict or set uses space proportional to the input — for speed.

Dicts require that keys are hashable: strings, numbers, and tuples of hashables all work. Lists do not — a list can change, which would break the hash. When you try to use a list as a key you'll get a TypeError: unhashable type: 'list'.

Where to go next

Next: solving with dicts — implementing the count and group patterns in Python, plus the classic "two sum" problem that shows exactly when O(n) beats O(n²).

Finished reading? Mark it complete to track your progress.

On this page