Everything the patterns assume you already know
The pattern pages move fast: they expect you to know what O(n) means, when to reach for a dict, and how a recursive function unwinds. This page teaches exactly those basics, from zero, with the same step-through visualizers. Skip any section you already know.
1. How this site teaches
Most interview problems are variations of about two dozen ideas called patterns: two pointers, sliding window, BFS, dynamic programming and so on. Each pattern has its own page with the same ten sections: what it's for, how it works, how to spot it, when not to use it, a Python template, memory hooks, solved LeetCode problems, common mistakes, a quiz and a practice list.
- Learn the foundations here. Big-O, the data structures, recursion and the Python you'll actually type.
- Follow the beginner path. It orders the 24 patterns from gentlest to hardest.
- On each pattern page, solve the Start here problems first. They're the gentlest 2–3 problems for that pattern.
- Press Play on every visualizer. Watching the pointers, windows and stacks move is faster than reading about them. Inside a visualizer, ← → step and space plays.
- Hover or tap a dotted word on any page to see a one-line definition. They're all collected in the glossary.
2. Big-O: is my solution fast enough?
Big-O describes how the number of steps grows as the input size n grows. It ignores constants and small terms, because for big inputs only the shape of the growth matters. You find it by counting how many times the work repeats:
| Code shape | Big-O | Why |
|---|---|---|
x = nums[i], d[key], x in my_set | O(1) | One step, whatever the size. |
| Halve the range every step (binary search) | O(log n) | A million halves to 1 in about 20 steps. |
| One loop over the input | O(n) | One step per element. |
sorted(nums), a heap push for every element | O(n log n) | n elements, each costing log n. |
| A loop inside a loop over the same input | O(n²) | n steps, each doing n steps. |
| Try every subset (each item: take it or not) | O(2ⁿ) | Each new item doubles the work. |
def has_duplicate_slow(nums): # O(n²): compares every pair
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] == nums[j]:
return True
return False
def has_duplicate_fast(nums): # O(n): one pass, O(1) set lookups
seen = set()
for x in nums:
if x in seen:
return True
seen.add(x)
return False
Drag the slider to see why this matters. Python does roughly ten million simple steps per second, and LeetCode usually allows a second or two.
n ≤ 10 → O(n!) or O(2ⁿ) is fine (backtracking) · n ≤ 20 → O(2ⁿ) · n ≤ 500 → O(n³) · n ≤ 5,000 → O(n²) · n ≤ 10⁵ or 10⁶ → O(n log n) or O(n). If a nested loop would be O(n²) and n is 10⁵, you need a pattern.Space complexity is the same idea for memory: how much extra storage you use as n grows. has_duplicate_fast trades O(n) memory (the set) for O(n) time. That trade, spending memory to save time, is behind half the patterns on this site.
3. The core data structures
A data structure is a way of storing values so that the operations you need are cheap. Picking the right one is often the whole solution. These are the ones interviews use, and what they cost:
| Structure | In Python | Good at | Cost |
|---|---|---|---|
| Array / list | [3, 1, 4] | Reading or writing position i; adding at the end | index O(1) · append O(1) · insert/remove at front O(n) · search O(n) |
| String | "abc" | Text; immutable, so build with a list and "".join | index O(1) · slicing O(k) · s += c in a loop O(n²) |
| Hash map | dict, Counter, defaultdict | "Have I seen this?", "how many?", "where was it?" | get / set / delete / in: O(1) on average |
| Hash set | set | Unique values, fast membership tests | add / remove / in: O(1) on average |
| Stack | list: append, pop | Last in, first out: brackets, undo, nesting | push / pop / peek O(1) |
| Queue | collections.deque | First in, first out: BFS, processing in arrival order | append / popleft O(1) (list.pop(0) is O(n)) |
| Heap | heapq on a list | Always getting the smallest (or largest) next | peek O(1) · push / pop O(log n) |
| Linked list | ListNode objects | Rewiring order without shifting elements | insert after a node O(1) · find the i-th O(n) |
| Tree | TreeNode objects | Hierarchies; sorted data (binary search trees) | visit all O(n) · BST search O(height) |
| Graph | dict of lists (adjacency list) | Networks: roads, friendships, dependencies | visit all O(nodes + edges) |
Stack and queue
Both hold items in a line. A stack adds and removes at the same end, so the newest item leaves first. A queue adds at the back and removes from the front, so the oldest leaves first.
Hash map: counting and remembering
A dict jumps straight to a key's value instead of searching for it. That turns "have I seen this before?" from a loop into one step, which is why it fixes so many slow nested loops.
Linked list: rewiring pointers
Each node knows only its value and the next node. There's no index, so you walk from the head. In exchange, inserting needs no shifting: just change two pointers, in the right order.
Heap: the smallest item, always on top
A heap is a binary tree stored in a plain list, where every parent is smaller than its children. The smallest item is always at index 0. A push adds at the end and swaps upward; a pop moves the last item to the top and swaps it down. Each takes O(log n) because the tree's height is log n.
Trees and graphs
A binary tree: node 1 is the root, 2 and 3 are its children, and 4, 5 and 6 are leaves (no children). Every node with everything below it is a subtree, which is why tree code is usually recursive.
A graph: nodes joined by edges, with no root and possibly cycles (A → B → C → A). Store it as an adjacency list: {"A": ["B", "D"], "B": ["C"], "C": ["A"], "D": []}.
4. Recursion, one call at a time
A recursive function solves a problem by calling itself on a smaller version of it. Every recursive function has the same three parts:
- Base case: the smallest input, answered directly. Without it the calls never stop.
- Smaller step: call yourself on a smaller input (n − 1, the left child, the rest of the list).
- Combine: trust that the smaller call returns the right answer, and build yours from it.
def factorial(n):
if n == 1: # base case: answer directly
return 1
return n * factorial(n - 1) # smaller step, then combine
Each call waits for the one it made. Python keeps the waiting calls on the call stack, a stack of paused calls with the newest on top:
When a function calls itself twice, the calls form a tree. Fibonacci is the classic example: fib(n) = fib(n − 1) + fib(n − 2). Watch how fib(2) is computed twice: that repeated work is exactly what dynamic programming removes by remembering answers (memoization).
def fib(n):
if n <= 1: # base cases: fib(0) = 0, fib(1) = 1
return n
return fib(n - 1) + fib(n - 2)
RecursionError. For deep inputs (a linked list of 10⁵ nodes, a long path) either add sys.setrecursionlimit(10**6) or rewrite the recursion with your own stack.Where you'll use it: tree traversal, DFS, backtracking and dynamic programming are all recursion with a twist.
5. The Python toolkit for interviews
You don't need much Python, but you need these few tools at your fingertips:
| Tool | What it's for | Example |
|---|---|---|
enumerate, zip, range | Loop with indices, walk two lists together, count down | for i, x in enumerate(nums) · range(n - 1, -1, -1) |
| Slicing | Sub-lists, copies, reversing (each makes a copy: O(k)) | nums[1:4] · s[::-1] · nums[:] |
dict / defaultdict | Remember positions, group things | groups = defaultdict(list); groups[key].append(x) |
Counter | Count everything in one line | Counter("banana")["a"] == 3 |
set | "Seen before?" in O(1) | if x in seen: ... |
deque | Queues for BFS | q = deque([start]); q.popleft() |
heapq | Smallest item next; top k | heappush(h, x) · heappop(h) · heappush(h, -x) for max |
sorted with key | Sort by any rule | sorted(intervals, key=lambda iv: iv[0]) |
bisect | Binary search in a sorted list | bisect_left(arr, x) = first index with value ≥ x |
@cache | Memoize a recursive function | from functools import cache |
float("inf") | A starting value for min / max | best = float("inf") |
"".join, split | Build strings efficiently, split words | "".join(parts) · s.split() |
from collections import Counter, defaultdict, deque
import heapq
counts = Counter("banana") # {'a': 3, 'n': 2, 'b': 1}
by_len = defaultdict(list)
for w in ["hi", "yo", "hey"]:
by_len[len(w)].append(w) # {2: ['hi', 'yo'], 3: ['hey']}
q = deque([1]) # queue: add right, remove left
q.append(2)
first = q.popleft() # 1
h = [] # heap: smallest always at h[0]
for x in [5, 1, 4]:
heapq.heappush(h, x)
smallest = heapq.heappop(h) # 1
grid = [[0] * 3 for _ in range(2)] # 2 rows x 3 columns, rows independent
[[0] * 3] * 2 makes two references to the same row: change one, both change. Use the list comprehension above. Also: // is integer division (7 // 2 == 3), list.pop(0) is O(n) (use a deque), and s += c inside a loop rebuilds the string every time.6. How to read a LeetCode problem
Every LeetCode problem has the same parts, and each one gives you information:
- The statement. Underline the input, the output and any words like sorted, contiguous, unique, at most k. Those words point to the pattern; the pattern finder lists them.
- The examples. Work one by hand before coding. If you can't do it on paper, you can't code it.
- The constraints. The size of
ntells you the Big-O you need (see section 2). Value ranges tell you about negatives, zeros and overflow. - The function signature. You fill in a method of a class called
Solution. LeetCode calls it with each test.
from typing import List, Optional
class Solution: # LeetCode gives you this shell
def twoSum(self, nums: List[int], target: int) -> List[int]:
... # your code goes here
# LeetCode's types, if you want to run tree or list problems yourself:
class ListNode:
def __init__(self, val=0, next=None):
self.val, self.next = val, next
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val, self.left, self.right = val, left, right
# To test on your own computer, call it like LeetCode does:
print(Solution().twoSum([2, 7, 11, 15], 9))
selfis just there because it's a method; you can ignore it.List[int],Optional[TreeNode]and-> intare type hints: notes about what goes in and comes out. Python doesn't enforce them.- "Return the answer modulo 10⁹ + 7" means
return ans % 1_000_000_007; it keeps huge counts small. - Problems marked Premium need a LeetCode subscription; on this site their statement and examples are written out so you can still learn them.
7. Your first problem: Two Sum, start to finish
Problem: given a list of numbers and a target, return the indices of the two numbers that add up to the target. Example: nums = [2, 7, 11, 15], target = 9 → [0, 1], because 2 + 7 = 9.
- Understand. Exactly one answer exists; you can't use the same element twice; return indices, not values.
- Brute force first. Try every pair: two nested loops, O(n²). Say it out loud in an interview; it shows you can solve it, and it gives you something to improve.
- Find the wasted work. For each number
x, the inner loop searches fortarget − x. Searching is what a hash map makes instant. - Improve. Walk once; for each
x, check whether its partnertarget − xwas already seen; otherwise rememberx's index. O(n) time, O(n) space. - Test on the example and the edge cases: negatives, duplicates like
[3, 3], the pair at the very end.
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# Brute force, O(n²):
# for i in range(len(nums)):
# for j in range(i + 1, len(nums)):
# if nums[i] + nums[j] == target:
# return [i, j]
seen = {} # value -> index where we saw it
for i, x in enumerate(nums):
if target - x in seen: # partner seen earlier: done
return [seen[target - x], i]
seen[x] = i # remember x for later numbers
That move, from "search again every time" to "remember what you've seen", is the Hash Map & Set pattern. Step through this exact solution, line by line, on its pattern page. This 5-step routine is the UMPIRE method: use it on every problem.
8. Self-check quiz
A loop over nums with another loop over nums inside it. What's the time complexity?
The constraints say n ≤ 10⁵. What should you aim for?
You need to check "have I seen this value before?" millions of times. Which structure?
Which should you use for a queue in Python?
list.pop(0) shifts every element: O(n). deque.popleft() is O(1).Your recursive function raises RecursionError on a tiny input. Most likely cause?
9. Glossary
Every dotted word on the site comes from this list. Hover or tap one anywhere to see its definition.
10. Where to go next
Follow the beginner path
The 24 patterns in the order to learn them, each with its first problems. Open the beginner path →
Start with pattern 21
Array & String Basics: careful scans and clean output, the skill every other pattern builds on.
Keep the cheat sheet open
All 24 templates and trigger words on one printable page.