Sorting a million elements, multiplying two very large integers, and locating a value in a sorted array share a structure: the input breaks into smaller instances of the identical problem, and the sub-answers reassemble into the whole. Divide-and-conquer is the control structure for that shape. It divides a size-n input into subproblems of the same kind, conquers each by recursing until a base case is small enough to solve directly, and combines the sub-results at cost f(n).

The subproblems have to be independent — each reads a disjoint slice of the input and never consults another’s answer. That independence, not the recursion itself, separates the paradigm from Dynamic Programming, whose subproblems overlap and must be cached. It also makes the analysis mechanical: every instance produces a recurrence T(n) = a·T(n/b) + f(n), and the Master Theorem reads the bound off a, b, and the combine cost.

Core shape: input → a independent subproblems of size n/b → recurse to a base case → combine at cost f(n)T(n) = a·T(n/b) + f(n).

A paradigm has no single input to step through; the structure a renderer would animate is the recursion tree itself.

Visualization pending

Planned StepTrace: a recursion-tree card that splits a problem into independent subproblems, solves each branch, then combines the results on the way back up — the split-and-merge shape of merge sort. No matching renderer exists in engine.js yet.

Divide, conquer, combine

The paradigm is three steps and a stopping rule:

  1. Divide — split the size-n input into a subproblems, each of size about n/b.
  2. Conquer — solve each subproblem by recursing. Below a size cutoff the recursion stops and solves the base case directly.
  3. Combine — merge the a sub-answers into the answer for size n, at cost f(n).

Which step carries the work varies by algorithm. Merge Sort splits at the midpoint for free and does everything in the combine, merging two sorted halves in O(n). Quick Sort inverts that: partitioning around a pivot is the work, and once both sides are sorted the combine is nothing. Binary Search has a single subproblem (a = 1) — each step discards the half that cannot hold the target and recurses into the other, which is why it is sometimes called decrease-and-conquer. Karatsuba multiplication, the FFT, and the closest-pair-of-points algorithm are the same skeleton with heavier divide or combine steps.

The subproblems are independent: they read disjoint slices of the input and never need one another’s results. Because the a recursive calls share no mutable state, they run on separate cores with no locking, which is why fork/join frameworks and map-style GPU kernels map onto divide-and-conquer directly. Overlapping subproblems would contend over shared state and lose that property.

Complexity via the Master Theorem

Because each subproblem is a scaled copy of the original, the running time satisfies T(n) = a·T(n/b) + f(n): a subproblems, each 1/b the size, plus f(n) to combine. The Master Theorem resolves it by comparing the combine cost f(n) against n^(log_b a), the total work across the leaves of the recursion tree. The larger term dominates.

CaseConditionResultWhere the work concentrates
1f(n) = O(n^(log_b a − ε))Θ(n^(log_b a))The leaves — most work is at the bottom of the tree.
2f(n) = Θ(n^(log_b a))Θ(n^(log_b a) · log n)Every level costs the same; the log n levels add the log factor.
3f(n) = Ω(n^(log_b a + ε)), regularΘ(f(n))The top-level combine dominates everything below.

Merge sort is 2T(n/2) + Θ(n): a = 2, b = 2, so the leaf work is n^(log_2 2) = n^1, and f(n) = Θ(n) matches it — Case 2, giving Θ(n log n). The same table applied to other instances:

InstanceRecurrencelog_b af(n)CaseResult
Merge Sort2T(n/2) + Θ(n)1n2Θ(n log n)
Binary SearchT(n/2) + Θ(1)0n^02Θ(log n)
Karatsuba multiply3T(n/2) + Θ(n)1.585n1Θ(n^1.585)
Strassen matrix7T(n/2) + Θ(n^2)2.807n^21Θ(n^2.807)

Karatsuba shows why a carries weight. Schoolbook multiplication is 4T(n/2), but replacing one of the four half-size multiplications with additions drops it to 3T(n/2), moving the exponent from 2 to log_2 3 ≈ 1.585. The count of subproblems sits in the exponent, not a constant factor, so a single dropped recursive call is a genuine asymptotic win; Strassen does the same for matrices, 8 multiplies down to 7.

Space is usually O(log n) for the recursion stack, plus whatever the combine allocates — O(n) for merge sort’s auxiliary buffer, O(1) for in-place partitioning.

When the paradigm is the wrong fit

Independence is a precondition, not a guarantee. If the subproblems overlap — calling each other’s subcalls — plain recursion recomputes the shared work on every branch. Naive Fibonacci is the standard case: fib(n-1) and fib(n-2) both recompute fib(n-3) and below, an exponential blowup for a problem that is linear once the repeated calls are cached. The recursive shape is identical to merge sort’s; the difference is that merge sort’s slices are disjoint and never recur, so memoising it saves nothing. Overlapping subproblems are the signal to switch to Dynamic Programming, whose entire purpose is to store and reuse them.

The combine step can dominate the recurrence. A “split in half” decomposition does not imply O(n log n). If f(n) grows faster than the leaf work n^(log_b a), the recurrence lands in Case 3 and the bound is Θ(f(n)), no better than the combine alone. The recurrence has to be written down and classified, not inferred from the split.

The base case is a performance boundary, not only a mathematical one. Recursing all the way to n = 1 spends more time on call frames than on useful work once a slice is small, and can overflow the stack on adversarial input. Production sorts stop well above the clean base case and hand small slices to Insertion Sort, whose tight loop beats recursion below roughly 16–32 elements — the fallback Introsort and Tim Sort both use.

Reference drawer

Questions

References