04 — Heaps, Priority Scheduling & Tries
At a glance
After completing this lesson you can:
- Implement priority scheduling and top-K analytics using binary heaps (
heapq). - Build prefix trees (Tries) for fast prefix autocompletion and route dispatching.
- Prevent sorting an entire dataset ($O(n \log n)$) when only top-$k$ elements ($O(n \log k)$) are needed.
Time: ~50 minutes · Prerequisites: 03 — Stacks, Queues & Ring Buffers
Reading Companion
- Book: Grokking Algorithms (2nd Edition) — Aditya Y. Bhargava
- Chapter 9: Dijkstra's Algorithm (Priority Queues, Weighted Graphs).
Why this matters in production
If an analytics endpoint calculates the "Top 10 slowest API endpoints" or "Top 5 most active tenants" across 1,000,000 requests by calling sorted(requests)[-10:], it wastes memory and CPU sorting 999,990 irrelevant items. Using a min-heap of size $k=10$ scales at $O(n \log k)$ time and $O(k)$ memory. Similarly, API routers (like FastAPI / Starlette routing tables) use Tries/Radix trees to match path prefixes (/api/v1/users/{id}) in $O(L)$ time (where $L$ is path length), independent of total route count.
Deep dive
1. Binary Min-Heaps (heapq)
A binary heap is a complete binary tree where each parent node is $\le$ its children (min-heap invariant):
- In Python,
heapqrepresents the heap inside a standardlist: - Parent index $i
ightarrow$ Children at $2i + 1$ and $2i + 2$.
- Insert (
heappush): $O(\log k)$. - Pop min (
heappop): $O(\log k)$. - Look at min (
h[0]): $O(1)$.
2. Prefix Trees (Tries)
A Trie organizes strings by character transitions:
Root
├── 'a' ── 'p' ── 'i' (endpoint)
│ └── '/' ── 'v' ── '1' (endpoint)
└── 'h' ── 'e' ── 'a' ── 'l' ── 't' ── 'h' (endpoint)
Lookup time is $O(L)$, where $L$ is the string length, completely independent of how many millions of words or routes exist in the system.
Hands on
python3 phase1-cs-and-architecture/01-data-structures/04-heaps-trees-and-tries/examples/01_demo.py
python3 phase1-cs-and-architecture/01-data-structures/04-heaps-trees-and-tries/examples/02_trap.py
AI Trap & Code Review
# BAD AI-GENERATED CODE — O(N log N) FULL SORT FOR TOP-K
def get_top_k_slowest(requests: list[dict], k: int) -> list[dict]:
# Sorts all 1,000,000 items in memory!
return sorted(requests, key=lambda r: r["latency_ms"], reverse=True)[:k]
Exercises
- Rung 1 (
top_k_elements_heap): Given an iterable of numbers, return the top $k$ largest elements in descending order usingheapqin $O(n \log k)$ time. - Rung 2 (
PriorityJobQueue— Fix the AI Bug): Build a thread-safe priority task queue where jobs with lower priority numbers execute first, breaking ties deterministically by submission order. - Rung 3 (
PrefixTrie): Implementinsert(word)andstarts_with(prefix) -> boolusing nested dictionary nodes. - Rung 4 (
PathRouterTrie): Build a prefix-based URL routing trie that matches paths like/api/v1/usersto handler functions. - Rung 5 (
SlidingWindowMedian): Compute rolling medians over a stream using two balancing heaps (max-heap for lower half, min-heap for upper half) in $O(\log k)$ per element.
Wrapping up
Can you explain?
- Why is a size-$k$ min-heap used to find the top-$k$ largest items?
- How does a Trie achieve $O(L)$ prefix matching regardless of dictionary size?
- What is the heapify operation and what is its time complexity?
Further Reading & References
- CPython Source: listobject.c (Dynamic Array Implementation)
- Raymond Hettinger: Modern Dictionaries by Key-Sharing
- Alex Xu: System Design Interview — Rate Limiter Design
- Tim Roughgarden: Algorithms Illuminated Series
Show reference solution notes
Solution & Engineering Notes — 04 Heaps & Tries
- Top-K Min-Heap Invariant: When seeking the $k$ largest elements, maintain a min-heap of size $k$. The root holds the smallest of the top-$k$; any new item larger than the root replaces it via
heappushpop. - Tie-breaking in Priority Queues: Always insert a monotonic counter
(priority, counter, item). If two tasks have the same priority number, Python comparescounter, preventing comparison crashes ifitemis an unorderable dictionary or object.
Self-check
Run python3 -m unittest phase1-cs-and-architecture/01-data-structures/04-heaps-trees-and-tries/test_exercises.py -v until green, then record it here. Saved to progress/rungs.json; commit it.
Completion edits PROGRESS.md; commit it with the lesson.