Home / Algorithms

Divide and Conquer

Split the problem, solve the pieces, combine the answers. It is the paradigm behind merge sort, quick sort and binary search — and the recursion tree shows exactly where the log n comes from.

Controls

array size n8

The Recursion Tree

step 0

Insight

Three phases at every level: divide into subproblems, conquer them recursively, combine the results.

T(n) = a·T(n/b) + f(n) ↑ ↑ ↑ pieces size combine
levels0
work / level0
total0

Complexity

Merge sort O(n log n)
Binary search O(log n)
Depth O(log n)

Overview

Divide and conquer splits a problem into smaller instances, solves them, and combines their results. It works when subproblems are easier to solve and their answers can be combined efficiently, as in merge sort.

The problem it solves

The canonical example is merge sort. It divides the array in half, recursively sorts each half, and merges the two sorted halves. The merge step does O(n) work, and there are O(log n) levels of recursion.

Where log n Comes From

The recursion tree makes it obvious. Each level halves the problem size, so it takes log₂n levels to reach size 1 — that is the height of the tree.

In merge sort, every level does O(n) total work (each element is touched once during merging), and there are log n levels. Multiply: O(n log n). Raise n in the lab and watch the level count grow by exactly one each time you double.

The Master Theorem

For recurrences of the form T(n) = a·T(n/b) + f(n) — a subproblems of size n/b, plus f(n) to combine — the answer depends on which dominates:

  • Leaves dominate — most work at the bottom.
  • Balanced — equal work per level, giving the extra log factor. Merge sort sits here: T(n) = 2T(n/2) + O(n) → O(n log n).
  • Root dominates — the combine step is the expensive part.

Binary search is T(n) = T(n/2) + O(1): one subproblem, trivial combine. So O(log n) total. It discards half the work rather than recursing into both halves. This is why it beats merge sort's cost.

The Combine Step Is Where the Work Lives

Dividing is usually trivial — compute a midpoint. The interesting engineering is almost always in combining.

  • Merge sort — merging two sorted halves is O(n) and needs O(n) scratch space.
  • Quick sort — inverts the pattern: it partitions before recursing. So the combine step is free. That is why it sorts in place.
  • Maximum subarray — the answer may straddle the midpoint, so combining must scan outward from the centre.

Why It Matters Beyond Sorting

Divide and conquer parallelises naturally — free subproblems can run on different cores. This is why it underpins MapReduce and most parallel frameworks. It is also the pattern behind Karatsuba multiplication, the Fast Fourier Transform, and closest-pair-of-points.

Split, solve, combine

Divide and conquer solves a problem by breaking it into free subproblems of the same kind, solving those recursively, and combining the results.

Three steps, and the third is where the algorithms differ:

Divide the problem into smaller instances. Conquer each recursively, until a base case is trivial. Combine the sub-results into the answer.

AlgorithmDivideCombine
Merge sortSplit in halfMerge two sorted halves — O(n)
Quick sortPartition around a pivotNothing — work done in the divide
Binary searchHalve the rangeNothing — one side is discarded
Karatsuba multiplicationSplit the digitsThree products and some additions
Strassen's matrix productSplit into quadrantsSeven products and additions
Closest pair of pointsSplit by x-coordinateCheck the strip near the boundary

Note that quick sort and merge sort are mirror images: quick sort does its work partitioning before recursing, merge sort does it merging afterwards. Binary search is the degenerate case where one subproblem is discarded fully. This is why it is O(log n) rather than O(n log n).

Experiments to try with Divide and Conquer

  1. Step through merge sort at n = 8. Watch the tree split down to single elements. Then merge back upward.
  2. Count the levels: 3 for n = 8. That is log₂8. And it is exactly where the log n factor comes from.

Common mistakes with Divide and Conquer

  • Using it where subproblems overlap. That is dynamic programming, and without caching the cost is exponential.
  • Forgetting the base case, giving infinite recursion.
  • An expensive combine step that dominates and negates the saving.
  • Recursion depth on large inputs in Python — convert to iteration or raise the limit.
  • Ignoring the constant factor. Recursion has overhead; below a threshold a simple loop wins. This is why real sorts switch to insertion sort for small subarrays.
  • Copying data at every level. arr[:mid] allocates; passing indices into one array avoids it.

Sources and next steps

Run it in Python

Two problems that look nothing alike solved by the same shape: fast exponentiation, which turns 1,000 multiplications into 10, and counting inversions during a merge, which does in n log n what the obvious loop does in n².

divide_and_conquer.pyPython 3
Output

How the code works

  1. half = power(base, exp // 2, depth + 1)One recursive call, and its result is used twice. Writing power(b, n//2) * power(b, n//2) instead looks identical and is exponentially slower — the saving is in reusing the value, not in the halving.
  2. if exp % 2: result *= baseHandles the odd case, where halving loses a factor. This is where off-by-one errors live in every implementation of this function.
  3. left, x = sort_and_count(a[:mid])The divide step. Each half is solved independently, and the returned count is the number of inversions within that half.
  4. z += len(left) - iThe combine step, and the clever line. When an item from the right half wins, every remaining item on the left is greater than it, so they are all inversions — counted in one addition instead of one by one.
  5. x + y + zLeft, right, and across. Every inversion is in exactly one of those three categories, which is the proof that the count is complete. That decomposition is the divide-and-conquer pattern in general.

Change one thing

  • Compute power(2, 1000). Ten recursive calls, and Python's unbounded integers print the whole 302-digit result.
  • Run the inversion counter on a reversed list of 12 items. The answer is n(n−1)/2, the maximum possible — and it is the same number insertion sort would charge you in shifts.
  • Time both counters on a list of 2,000 random numbers. The brute force does two million comparisons; the merge does about twenty thousand.

Where this runs

Real CPython, compiled to WebAssembly and running on your own machine — nothing is uploaded. The first run takes a few seconds while the interpreter downloads; after that it is immediate. Need more room, or want to paste your own attempt? Use the Python compiler.

Predict, then reveal

About to run: Step through merge sort at n = 8. Before it does — what happens to the readout?

Committing to an answer first is the point — the reveal runs the experiment on the visualisation above and reads the real value back, so nothing here is scripted.

Check yourself

0 of 3

Answer without scrolling back up.

  1. Why does exponentiation by squaring make ONE recursive call and reuse the result?

  2. Counting inversions during a merge is O(n log n) because, when an item from the right half wins:

  3. Divide and conquer proves its answer complete by showing every case falls into:

Cheat sheet

Divide and Conquer

Split a problem in half, recurse, combine — and see how halving at each level gives merge sort its O(n log n) guarantee.

ALGORITHMS · vizlearn.in/dsa/divide_and_conquer.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.