In the previous lesson you learned to choose structures with judgment; in this one we'll look back calmly. We'll review the entire course by telling it as what it has really been: the story of TaskFlow, an application that started out as a plain Python dictionary and ended up with a board, undo, notifications, indexes, hierarchies and a dependency scheduler. Seeing the whole journey at once fixes the mental map better than any list of definitions, and the final self-assessment quiz will tell you honestly what you have down cold and what deserves a review before the projects.
Contents
- The evolution of TaskFlow, module by module
- Cheat-sheet table: everything we've built
- The five cross-cutting concepts
- What you can do now that you couldn't before
- Self-assessment quiz
The evolution of TaskFlow, module by module
flowchart LR
V0[v0.1<br/>task = dict] --> V1[M1-M2<br/>board<br/>lists] --> V2[M3<br/>undo<br/>stacks] --> V3[M4<br/>notifications<br/>queues and heaps]
V3 --> V4[M5<br/>indexes<br/>hashing] --> V5[M6<br/>hierarchies<br/>trees] --> V6[M7<br/>scheduler<br/>graphs] --> V7[M8<br/>judgment<br/>and projects]
Module 1 — The foundations: ADTs, Big O and memory
TaskFlow was born as a humble dict: {"id": 1, "title": "...", "priority": 2, "status": "pending"}. Before building anything we learned to think: the difference between an ADT's contract and its implementation, Big O notation from O(1) to O(n²), the cost table for list/dict/set, and how to actually measure with timeit. We also looked memory in the eye: an array is pure contiguity (base + i × size, hence O(1) access), Python's list is a dynamic array, and insert(0)/pop(0) hide an O(n) we would chase for the rest of the course.
Module 2 — The board: linked lists
The first real problem: inserting and removing tasks from the board without paying for shifts. With Node and LinkedList we built TaskFlow's board and accepted the linked-list bargain: O(1) insertion at the head in exchange for O(n) access by position. DoubleNode and DoublyLinkedList gave us the TaskHistory, navigable in both directions, and the CircularList gave us the round-robin TaskDispatcher. As a bonus, the classics: reversing the list, detecting cycles with Floyd's algorithm (tortoise and hare), merging sorted lists and insert_sorted.
Module 3 — Undo: stacks
"Ctrl+Z" is the star operation of any editor, and its structure is the stack: the last thing you did is the first thing that gets undone. On top of Stack and LinkedStack we assembled the ActionHistory and the UndoRedoManager with two stacks (undo pushes onto redo, and vice versa). We saw that stacks also validate (is_balanced_filter), compute (eval_postfix, infix_to_postfix), remember extremes (MinStack) and hold up the program's very execution: the call stack explains recursion, RecursionError, and how to turn recursion into iteration with an explicit stack.
Module 4 — Notifications: queues
Notifications demanded FIFO fairness: Queue on top of LinkedList gave us the NotificationQueue, and along the way we proved that two stacks make a queue (TwoStackQueue). The CircularQueue (ring buffer) solved the EventLog with fixed memory. When "in arrival order" stopped being enough, priority queues arrived: from the naive version to heapq with (priority, counter, task) tuples in the UrgentInbox. And collections.deque proved to be the Swiss Army knife of the ends: BoundedHistory with maxlen, the moving-average ProductivityWindow, and the monotonic deque.
Module 5 — Indexes: hash tables
Finding a task by id by walking lists was O(n); the O(1) magic of dict stopped being magic when we built a hash table from scratch: NaiveHashTable, polynomial hashing, collisions with chaining, a 0.75 load factor and rehashing in HashTable. In practice: hashable keys, defaultdict, Counter, the tag→ids inverted index of TagSearch, memoization and two-sum. And the honest limit that opened the next module: hashing knows nothing about order and can't do ranges.
Module 6 — Hierarchies: trees
Projects contain tasks that contain subtasks: pure hierarchy. TreeNode modeled the general tree and budget showed the power of postorder (children before the parent). With BinaryNode came the tree types, the array representation (children at 2i+1/2i+2) and the four traversals, iterative versions with a stack included. The SearchTree (BST) finally answered "priority between 1 and 3" with range_query, the AVLTree guaranteed O(log n) with rotations, B/B+ trees explained database indexes, and our own Heap (sift up/sift down) rebuilt heapq from the inside for UrgentInbox 2.0. Classics: is_bst, rebuild, top_k.
Module 7 — Dependencies: graphs
"Task B can't start until A is finished" isn't hierarchy: it's a graph. The Graph class (an adjacency list as a dict of dicts, with the convention "edge A→B = B depends on A") modeled TaskFlow's DAG. BFS with deque, recursive and iterative DFS, has_cycle with white/gray/black colors and Kahn's topological_order put the work in order; connected_components grouped; dijkstra, bellman_ford and the mention of Floyd-Warshall measured paths; prim, kruskal and UnionFind wove minimum networks. The finishing touch: the complete scheduler with parallel levels and critical_path, plus suggest_collaborators, pagerank and implicit grid graphs.
Module 8 — The judgment
This module: the seven-question method, the decision tree, the big table, and the projects ahead.
Cheat-sheet table: everything we've built
Your quick-reference index. If a name doesn't immediately bring its idea to mind, that's the lesson to review.
| Identifier | Module | What it does |
|---|---|---|
Node / LinkedList |
2 | Singly linked list; TaskFlow's first board |
DoubleNode / DoublyLinkedList |
2 | Links in both directions; the basis of TaskHistory |
CircularList / TaskDispatcher |
2 | Round-robin: handing out tasks in turns |
Stack / LinkedStack |
3 | LIFO on top of a list and on top of nodes |
ActionHistory / UndoRedoManager |
3 | Undo/redo with two stacks |
is_balanced_filter, eval_postfix, infix_to_postfix |
3 | Validating and evaluating expressions with a stack |
MinStack |
3 | Minimum in O(1) with an auxiliary stack |
Queue / NotificationQueue |
4 | FIFO on top of a linked list |
TwoStackQueue |
4 | A queue built from two stacks |
CircularQueue / EventLog |
4 | Fixed-memory ring buffer |
UrgentInbox |
4 and 6 | Priority queue with heapq; 2.0 with our own heap |
BoundedHistory / ProductivityWindow |
4 | deque with maxlen and a moving average |
NaiveHashTable / HashTable |
5 | Hashing from scratch: chaining, 0.75 load, rehashing |
TagSearch |
5 | Inverted index tag→ids |
TreeNode / budget |
6 | General project tree; postorder aggregation |
BinaryNode |
6 | Binary tree and traversals |
SearchTree (+ range_query) |
6 | BST with range queries |
AVLTree |
6 | Self-balancing BST: O(log n) guaranteed |
Heap |
6 | Our own binary heap: sift up/sift down |
is_bst, rebuild, top_k |
6 | Tree classics |
Graph |
7 | Adjacency list (dict of dicts) |
has_cycle, topological_order |
7 | White/gray/black colors; Kahn's algorithm |
dijkstra, bellman_ford |
7 | Shortest paths (without/with negative weights) |
prim, kruskal, UnionFind |
7 | Minimum spanning trees |
critical_path, level-based scheduler |
7 | Scheduling TaskFlow's DAG |
TaskFlowCore |
8 | Combining dict + indexes + heap |
The five cross-cutting concepts
More important than any specific structure, because they're what you'll keep using when structures this course doesn't cover show up:
- ADT versus implementation. A stack is a contract (push/pop/peek);
list,LinkedStackor adequeare ways to fulfill it. Program against the contract and you'll be able to swap the implementation without touching the rest of the code. - Big O as a language. It's not decorative math: it's how you predict whether code that works with 100 elements will survive 100,000. The difference between
inon alist(O(n)) and on aset(O(1)) decides whether your loop is linear or quadratic. - Recursion ↔ iteration. Every recursion is an implicit stack; every recursion can be rewritten with an explicit stack (and sometimes should be — remember
RecursionError). Tree traversals and DFS are the same pattern in two outfits. - Structures that build structures. The queue on a linked list, the queue made of two stacks, the min-tracking stack, the heap on an array, the graph on a dict of dicts, the LRU cache with a dict + doubly linked list... Composing is the skill; memorizing, just the shortcut.
- Measure instead of assuming.
timeitwas the course's first tool and should be the last word in any performance discussion. Theory points the way; real data decides.
What you can do now that you couldn't before
An honest list: you've actually done all of this, not just read about it.
- Read and write Big O analyses of your own code, and verify the theory with
timeit. - Implement from scratch, in Python: linked lists (singly, doubly, circular), stacks, queues, ring buffers, hash tables with collisions and rehashing, BSTs, AVL trees, heaps and graphs.
- Choose among
list,dict,set,dequeandheapqknowing what's underneath and what each operation costs. - Recognize patterns: LIFO → stack, FIFO → queue, "the most urgent" → heap, exact key → hash, ranges → tree, dependencies → graph.
- Apply the classic algorithms: Floyd's, tree traversals, BFS/DFS, cycle detection, topological order, Dijkstra, Bellman-Ford, Prim, Kruskal.
- Combine coordinated structures to satisfy several cost requirements at once.
- And what you don't know yet (sorting in depth, dynamic programming, tries...): you have it located, and the next lesson gives you the map to get there.
Common Mistakes and Tips
- Confusing "sounds familiar" with "I know it". The quiz below is only useful if you take it without looking; passive review produces a false sense of mastery.
- Reviewing structures as isolated flashcards. Review problems: "how would I build undo?", "how would I detect the cycle?". The structure should come to you from the problem, not the other way around.
- Skipping the whys. Knowing that
dictis O(1) without remembering polynomial hashing and rehashing is fragile knowledge: it collapses at the first follow-up question in an interview. - Not reimplementing from memory. Before the projects, try rewriting a
Stack, aCircularQueueand BFS without looking. Wherever you get stuck, that's your real gap.
Exercises
Self-assessment quiz. Answer without consulting the lessons, then check against the reasoned solutions.
- In a Python
listwith 1,000,000 elements, which of these operations is the most expensive? (a)lst[500000](b)lst.append(x)(c)lst.insert(0, x)(d)lst.pop() - The
UndoRedoManageruses two stacks. When "undo" runs, what exactly happens? - Why does the
UrgentInboxenqueue(priority, counter, task)tuples and not(priority, task)? - A colleague stores the ids of completed tasks in a
listand checksid in completedinside a loop over 50,000 tasks. What is the total cost and how do you fix it? - Which structure answers "give me all the tasks with priority between 1 and 3" in O(log n + k), and why can't a
dict? - You insert the keys 1, 2, 3, 4, 5 into a BST, in that order. What shape does the tree have, and what does it now cost to find 5? Which structure avoids this?
- Which traversal did
budgetuse to add up the cost of projects → tasks → subtasks, and why precisely that one? - In TaskFlow's dependency DAG, which algorithm gives you a valid execution order, and what signal warns you that no such order exists?
- True or false? "A heap keeps all of its elements completely sorted."
- Which two structures did the "review mode" cache combine (exercise 3 of the previous lesson), and what does each one contribute?
Solutions
- (c)
insert(0, x)is O(n): it shifts the million elements one position over. (a) is O(1) thanks to contiguity (base + i × size); (b) and (d) are O(1) (amortized for append) because they only touch the end. - The undo stack is
popped (the most recent action), its effect is reverted, and that action ispushed onto the redo stack. If the user then performs a new action, the redo stack is emptied: the alternative history is no longer valid. - Two reasons: the counter breaks ties between equal priorities, guaranteeing arrival order (FIFO within the same priority), and it also prevents
heapqfrom trying to compare the task dicts with each other (dicts aren't comparable and it would raiseTypeError). inon alistis O(n); inside the loop, O(n·m) — with 50,000 tasks, on the order of billions of comparisons. You convertcompletedinto asetonce (O(m)) and each check drops to O(1): the total becomes O(n + m).- A BST/AVL (or a sorted list with
bisect): you descend to the start of the range in O(log n) and walk the k results inorder. Adictcan't because the hash function scatters on purpose: it destroys every ordering relationship between keys to achieve O(1) by exact key. - It degenerates into a right-leaning "list": each key is greater than the previous one, so everything hangs off the right child. Finding 5 costs O(n). The AVL avoids it: it rotates when it detects imbalance and guarantees O(log n) height no matter how the input arrives.
- Postorder: it processes the children before the parent, so that when it's time to compute a node's budget, the budgets of all its subtrees are already computed and it's enough to add them up.
- The topological order (Kahn: keep extracting nodes with in-degree 0). If the algorithm finishes without having processed every vertex — or if the color-based DFS finds an edge to a gray node — there is a dependency cycle and no valid order exists.
- False. A heap only guarantees the heap property: each parent ≤ its children (in a min-heap). The minimum is at the root, but siblings have no order among themselves; that's why insert and extract are O(log n) rather than O(n) — it's a "sufficient" order, not a total one.
- A
dictid→node (locate in O(1)) and a doubly linked list holding the usage order (move a node to the head and evict from the tail in O(1)). The dict knows nothing about order; the list can't search: together, every operation is O(1).
Interpreting your score: 9-10 correct, you're ready for the projects; 6-8, review the lessons behind the questions you missed; below 6, go back to the exercises of your weak modules before continuing — the final projects assume all of this.
Conclusion
TaskFlow has gone from a loose dict to a system with seven families of structures working in coordination, and you have gone from using list "just because" to justifying every choice with operations and costs. The map is complete: the foundations (module 1), the linear structures (2-4), the indexes (5), the hierarchies (6), the networks (7) and the judgment (8). In the next lesson I leave you the library: the resources to keep growing on your own once this course ends.
Data Structures Course
Module 1: Introduction to Data Structures
- What Are Data Structures?
- The Importance of Data Structures in Programming
- Types of Data Structures
- Algorithmic Complexity and Big O Notation
- Arrays and Memory: the Foundation of Data Structures
Module 2: Lists
Module 3: Stacks
- Introduction to Stacks
- Basic Stack Operations
- Stack Implementation
- Stack Applications
- Stack Exercises
Module 4: Queues
- Introduction to Queues
- Basic Queue Operations
- Circular Queues
- Priority Queues
- Double-Ended Queues (Deques)
- Queue Exercises
Module 5: Hash Tables and Dictionaries
- Introduction to Hash Tables
- Hash Functions and Collision Resolution
- Dictionaries and Sets in Practice
- Hash Table Exercises
Module 6: Trees
- Introduction to Trees
- Binary Trees
- Tree Traversals
- Binary Search Trees
- AVL Trees
- B-Trees
- Heaps
- Tree Exercises
Module 7: Graphs
- Introduction to Graphs
- Graph Representation
- Graph Search Algorithms
- Shortest Path Algorithms
- Minimum Spanning Trees
- Graph Applications
- Graph Exercises
