Chapter 10

Data Structures

High School
At a glance
Core ideaEach structure makes some operations cheap and others costly.
Key termStack (LIFO) & queue (FIFO) — two opposite access orders.
You can…Pick the right structure and implement it from scratch.
Watch outHash maps are O(1) on average — the worst case is O(n).
Theory

A 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.
( [ { push(x) pop() → x top → bottom →
A stack only lets you push or pop from the top — the last item in is always the first one out.
Explanation

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
Practical

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.

Q&A
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.

Concept mind map

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.

arraystack LIFOqueue FIFOlinked listhash maptradeoffsData structures
Infographic

The key facts, visualised

Stack
LIFO: last in, first out; push and pop at one end.
Queue
FIFO: first in, first out; enqueue at back, dequeue at front.
Array index
O(1) access by position, but O(n) to insert in the middle.
Hash map
Average O(1) lookup by key using a hash function.
Solved examples

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?

  1. Stack after pushes (top last): 1, 2, 3.
  2. Pop removes the top, which is 3.

Example 2Enqueue A, B, C into a queue, then dequeue once.

  1. Queue front to back: A, B, C.
  2. Dequeue removes the front, A.
Practice problem set

Now you try

Work each one out first, then tap to reveal the worked answer.

1What is the difference between a stack and a queue?
A stack is LIFO (last in, first out); a queue is FIFO (first in, first out).
2Why is array access by index fast?
The position maps directly to a memory address, giving O(1) access.
3What is a weakness of arrays?
Inserting or deleting in the middle costs O(n) because elements must shift.
4When would you use a stack?
For undo history, matching brackets, or function calls, where you need the most recent item first.
5What does a hash map give you?
Average O(1) lookup, insert and delete by key, at the cost of no natural ordering.
6What advantage does a linked list have over an array?
Inserting or removing at a known node is O(1) without shifting, though random access is O(n).