Data Structures
High SchoolA data structure is a way of organising data so that particular operations are efficient. The choice of structure is often the single biggest factor in a program's speed. The fundamentals:
- Array — a contiguous block; access element i in O(1) by computing its address, but insertion in the middle costs O(n) because elements must shift.
- Linked list — nodes each holding a value and a pointer to the next; O(1) insertion once you hold the spot, but O(n) to find a position (no index arithmetic).
- Stack — Last-In-First-Out (LIFO); push and pop at one end. Models function calls, undo, backtracking.
- Queue — First-In-First-Out (FIFO); enqueue at the back, dequeue at the front. Models fairness, buffering, breadth-first search.
- Hash map — key→value store giving average O(1) lookup by hashing the key to a bucket.
- Tree — a hierarchy of nodes; a binary search tree keeps keys ordered for O(log n) search when balanced.
The recurring theme is trade-offs: no structure is best at everything. Arrays win at random access and lose at insertion; linked lists are the reverse. A hash map buys near-instant lookup at the cost of order and worst-case guarantees. Choosing a data structure is choosing which operations you want to be cheap, accepting that others become expensive.
The hash map deserves special respect: it turns "search a haystack" into "compute an address." A hash function scrambles a key into a bucket index; you go straight there. Collisions (two keys landing in one bucket) are handled by chaining or probing, and a good hash keeps them rare — which is why dictionaries feel instantaneous even with millions of entries.
Engineer's maxim. "Choose the right data structure and the algorithm writes itself." Fred Brooks put it sharply: show me your tables and I won't usually need your flowchart.
Array
- One contiguous block in memory
- O(1) random access by index
- O(n) to insert or delete in the middle
- Cache-friendly; capacity is fixed
Linked list
- Scattered nodes joined by pointers
- O(n) to reach the i-th element
- O(1) insert or delete once you hold the spot
- Grows freely; extra pointer overhead
We implement a stack and a queue from scratch, then use the stack to check whether brackets in an expression are balanced — a real parsing task.
class Stack:
def __init__(self):
self.items = []
def push(self, x): self.items.append(x) # add to top
def pop(self): return self.items.pop() # remove from top (LIFO)
def peek(self): return self.items[-1]
def is_empty(self): return len(self.items) == 0
def balanced(expr):
"""Return True if every bracket is correctly matched and nested."""
pairs = {')': '(', ']': '[', '}': '{'}
stack = Stack()
for ch in expr:
if ch in "([{":
stack.push(ch) # opening -> remember it
elif ch in ")]}":
if stack.is_empty():
return False # closing with nothing open
if stack.pop() != pairs[ch]:
return False # mismatched kind
return stack.is_empty() # nothing left unclosed
print(balanced("a(b[c]{d})e")) # True
print(balanced("(]")) # False (wrong kind)
print(balanced("((")) # False (unclosed)
# A queue: FIFO using two ends of a list
from collections import deque
q = deque()
q.append("first"); q.append("second") # enqueue at back
print(q.popleft()) # dequeue front -> "first"
The bracket-matcher is why stacks matter: the "last opened, first closed" nesting of brackets is exactly LIFO. Compilers use this same idea to parse nested structure in every program you write.
Q1 Why is array access O(1) but linked-list access O(n)?
An array is contiguous, so element i lives at base + i × size — one multiplication and one memory read, regardless of i. A linked list scatters nodes anywhere in memory connected by pointers, so to reach the i-th node you must start at the head and follow i pointers, one at a time.
Q2 How can a hash map be O(1) if there are collisions?
It is O(1) on average, not in the worst case. With a good hash function and a load factor kept low (by resizing), collisions are rare and each bucket holds only a handful of items, so lookup is effectively constant. In a pathological worst case — all keys colliding — it degrades to O(n), which is why cryptographically weak hashes can be attacked.
Q3 When would I choose a queue over a stack?
Use a queue when order of arrival must be preserved — print jobs, network packets, breadth-first search, any "fair" first-come-first-served processing. Use a stack when the most recent thing must be handled first — undo history, function-call return addresses, depth-first search, and evaluating nested structure.
Q4 What makes a binary search tree fast, and when is it slow?
At each node, comparing your key sends you left (smaller) or right (larger), halving the remaining candidates — O(log n) when the tree is balanced. But if you insert already-sorted data, it degenerates into a straight line (effectively a linked list) and search becomes O(n). Self-balancing variants (AVL, red-black trees) restore the guarantee by rotating nodes to keep the height near log n.
How the ideas connect
Every key idea in this chapter, branching from the core concept — use it to see the whole picture at a glance.
The key facts, visualised
Worked problems, step by step
Follow each solution line by line, then try to reproduce it on paper before moving on.
Example 1Push 1, 2, 3 onto a stack, then pop once. What comes out and what remains?
- Stack after pushes (top last): 1, 2, 3.
- Pop removes the top, which is 3.
Example 2Enqueue A, B, C into a queue, then dequeue once.
- Queue front to back: A, B, C.
- Dequeue removes the front, A.
Now you try
Work each one out first, then tap to reveal the worked answer.