Recurrence Relations

Master Theorem

What Problem Does the Master Theorem Solve?

A divide-and-conquer algorithm describes its own cost in terms of itself. Merge sort sorts an array by sorting two half-arrays and merging the results, so its running time obeys T(n) = 2T(n/2) + n. That equation is called a recurrence relation, and it is not an answer — you cannot look at it and say how fast merge sort is.

Solving the recurrence means turning it into a closed form like Θ(n log n). There are three standard ways to do that: expand it into a recursion tree and sum the levels, guess an answer and prove it by induction (the substitution method), or — when the recurrence has the right shape — apply the Master Theorem and simply read the answer off. This page is about the third.

The Master Theorem is a lookup table, not a technique. Its entire value is that somebody already did the recursion tree summation in general, so you can skip straight to the result.

The Standard Form

The theorem applies to recurrences that can be written in exactly this shape:

T(n) = a·T(n/b) + f(n), where a ≥ 1 and b > 1 are constants
  • a — how many subproblems each call creates.
    • Merge sort makes two recursive calls, so a = 2. It must be a constant ≥ 1; it does not have to equal b.
  • b — the factor by which the input shrinks.
    • Halving the array means b = 2. It must be a constant greater than 1, otherwise the recursion never reaches the base case.
  • f(n) — everything the call does outside the recursion.
    • The splitting, the merging, the partitioning, the combining. For merge sort this is the O(n) merge.

If your recurrence does not fit this template — different-sized subproblems, a subtractive step, a non-constant a — the Master Theorem does not apply, and no amount of rearranging will change that. The section on limitations below covers what to do instead.

Reading a, b and f(n) Off the Code

In practice you extract the recurrence from the function body. Every recursive call contributes to a and b; everything else contributes to f(n):

function mergeSort(arr) {
  if (arr.length <= 1) return arr;              // base case: O(1)

  const mid  = Math.floor(arr.length / 2);
  const left  = mergeSort(arr.slice(0, mid));   // a = 2 calls
  const right = mergeSort(arr.slice(mid));      // b = 2 (half the size)

  return merge(left, right);                    // f(n) = O(n)
}

Two calls on half the input, plus a linear merge: T(n) = 2T(n/2) + n.

function binarySearch(arr, target, low, high) {
  if (low > high) return -1;                    // base case: O(1)

  const mid = Math.floor((low + high) / 2);     // f(n) = O(1)
  if (arr[mid] === target) return mid;

  return arr[mid] < target                      // a = 1 call
    ? binarySearch(arr, target, mid + 1, high)  // b = 2 (half the range)
    : binarySearch(arr, target, low, mid - 1);
}

One call on half the input, constant work outside: T(n) = T(n/2) + 1.

Where the Three Cases Come From

Picture the recursion as a tree. The root is the original problem, each node has a children, and sizes shrink by a factor of b as you descend:

nn/2n/2n/4n/4n/4n/4= n= n= n
T(n) = 2T(n/2) + n. Each level doubles the number of subproblems and halves their size, so every level costs n. With log₂n levels, the total is n log n.
LevelSubproblemsSize of eachCost of the level
01nf(n)
1an/ba · f(n/b)
2n/b²a² · f(n/b²)
iaⁱn/bⁱaⁱ · f(n/bⁱ)
log_b n (leaves)a^(log_b n) = n^(log_b a)1Θ(n^(log_b a))

The recursion stops when the size reaches 1, which takes log_b n levels, and the bottom level holds n^(log_b a) leaves. So the total cost is a contest between two quantities: the work at the leaves, which is Θ(n^(log_b a)), and the work at the root, which is f(n).

That contest has exactly three outcomes, and those outcomes are the three cases. Either the leaves win, or the root wins, or neither does and every level contributes equally.

The Three Cases

CaseCondition on f(n)What it meansResult
Case 1f(n) = O(n^(log_b a − ε))The leaves dominateT(n) = Θ(n^(log_b a))
Case 2f(n) = Θ(n^(log_b a))Every level costs the sameT(n) = Θ(n^(log_b a) · log n)
Case 3f(n) = Ω(n^(log_b a + ε)) and regularity holdsThe root dominatesT(n) = Θ(f(n))

The function n^(log_b a) that f is compared against is sometimes called the watershed function. Computing it is always the first step, because all three conditions are stated relative to it.

The ε in Cases 1 and 3 matters more than it looks. It requires f to be polynomiallysmaller or larger — smaller or larger by a factor of n^ε for some ε > 0. Being smaller by a factor of log n does not qualify, and that is precisely the gap where the theorem gives no answer at all.

Case 2 also has a commonly used extended form: if f(n) = Θ(n^(log_b a) · log^k n) for some k ≥ 0, then T(n) = Θ(n^(log_b a) · log^(k+1) n). This is what handles T(n) = 2T(n/2) + n log n, giving Θ(n log² n).

Worked Example — Case 2 (Merge Sort)

Solve T(n) = 2T(n/2) + n.

  1. Identify the parts.
    • a = 2, b = 2, f(n) = n.
  2. Compute the watershed function.
    • n^(log_b a) = n^(log₂2) = n¹ = n.
  3. Compare.
    • f(n) = n and the watershed is n. They are the same, so Case 2 applies.
  4. Apply Case 2.
    • T(n) = Θ(n^(log_b a) · log n) = Θ(n log n).

This matches the recursion tree above: every level costs n, and there are log₂n levels.

Worked Example — Case 1 (Leaves Dominate)

Solve T(n) = 4T(n/2) + n.

  1. Identify the parts.
    • a = 4, b = 2, f(n) = n.
  2. Compute the watershed function.
    • n^(log₂4) = n² — four subproblems of half the size produce n² leaves.
  3. Compare.
    • f(n) = n is polynomially smaller than n²: n = O(n^(2−ε)) with ε = 1. Case 1 applies.
  4. Apply Case 1.
    • T(n) = Θ(n^(log_b a)) = Θ(n²).

The linear work at the root is irrelevant here — the tree branches so fast that almost all the cost sits in the leaves.

Worked Example — Case 3 (Root Dominates)

Solve T(n) = 2T(n/2) + n².

  1. Identify the parts.
    • a = 2, b = 2, f(n) = n².
  2. Compute the watershed function.
    • n^(log₂2) = n.
  3. Compare.
    • f(n) = n² is polynomially larger than n: n² = Ω(n^(1+ε)) with ε = 1. Case 3 is a candidate.
  4. Check the regularity condition.
    • a·f(n/b) = 2·(n/2)² = n²/2 ≤ c·n² holds with c = 1/2 < 1, so the condition is satisfied.
  5. Apply Case 3.
    • T(n) = Θ(f(n)) = Θ(n²).

Here the quadratic work at the top level swamps everything below it, so the recursion contributes nothing asymptotically.

A Reference Table of Common Recurrences

Recurrenceabn^(log_b a)CaseSolutionAlgorithm
T(n) = 2T(n/2) + n22Case 2Θ(n log n)Merge sort
T(n) = T(n/2) + 112n⁰ = 1Case 2Θ(log n)Binary search
T(n) = 4T(n/2) + n42Case 1Θ(n²)
T(n) = 9T(n/3) + n93Case 1Θ(n²)
T(n) = 7T(n/2) + n²72n^2.807Case 1Θ(n^log₂7)Strassen
T(n) = 2T(n/2) + n²22Case 3Θ(n²)
T(n) = 3T(n/4) + n log n34n^0.793Case 3Θ(n log n)
T(n) = 8T(n/2) + n³82Case 2Θ(n³ log n)

Strassen's matrix multiplication is the most interesting row. It replaces eight recursive multiplications with seven, dropping a from 8 to 7 — and since the answer is Θ(n^(log₂a)), that single change takes the running time from Θ(n³) to roughly Θ(n^2.807). It is a direct, practical demonstration that a is the parameter worth fighting over.

A Recipe You Can Follow Every Time

  1. Put the recurrence in standard form and read off a, b and f(n).
    • T(n) = aT(n/b) + f(n), with a ≥ 1 and b > 1 both constant.
  2. Compute the watershed function n^(log_b a).
    • This is the total cost of the leaves. Use log_b a = log a / log b if the exponent is not obvious.
  3. Compare f(n) with n^(log_b a).
    • Polynomially smaller → Case 1. The same → Case 2. Polynomially larger → Case 3.
  4. For Case 3 only, verify the regularity condition.
    • a · f(n/b) ≤ c · f(n) for some constant c < 1 and all large n.
  5. Write the answer that the matching case gives you.
    • Case 1 → Θ(n^(log_b a)); Case 2 → Θ(n^(log_b a) log n); Case 3 → Θ(f(n)).

When the Master Theorem Does Not Apply

The theorem is narrow by design, and recognising when it does not fit is as important as applying it when it does:

RecurrenceWhy it failsWhat to do instead
T(n) = 2T(n/2) + n/log nf is smaller than n but not polynomially smallerFalls in the gap below Case 2. (The true answer, Θ(n log log n), needs a recursion tree.)
T(n) = 2ⁿT(n/2) + na is not a constantThe theorem assumes a fixed number of subproblems.
T(n) = 0.5T(n/2) + na < 1You cannot have half a subproblem.
T(n) = T(n − 1) + nThe input shrinks by subtraction, not divisionThis is a decrease-and-conquer recurrence; use the substitution method instead.
T(n) = T(n/3) + T(2n/3) + nThe subproblems have different sizesThe theorem needs every subproblem to be the same size. Use a recursion tree.
T(n) = T(n/2) + n(2 − cos n)The regularity condition failsf grows fast enough for Case 3, but it oscillates, so a·f(n/b) ≤ c·f(n) does not hold.

The first row is the classic gap case. Here f(n) = n/log n is smaller than the watershed n, but only by a logarithmic factor, not a polynomial one — so Case 1 does not apply, and it is not equal to n either, so Case 2 does not apply. The theorem is simply silent, and you need a recursion tree to find the real answer.

Common Mistakes

  • Forgetting that the difference must be polynomial.
    • Case 1 needs f to be smaller by a factor of n^ε for some ε > 0. Being smaller by a factor of log n is not enough, and that recurrence falls into the gap where the theorem says nothing.
  • Skipping the regularity condition in Case 3.
    • It holds for every polynomial f, which is why it is easy to forget — but it is part of the case, and there are standard exercises built on functions where it fails.
  • Applying it to subtract-and-conquer recurrences.
    • T(n) = T(n − 1) + n is not of the form aT(n/b) + f(n). The Master Theorem cannot touch it.
  • Mixing up a and b.
    • a is how many calls you make; b is how much smaller each one is. In T(n) = 4T(n/2) + n they are different numbers, and swapping them changes the answer from Θ(n²) to Θ(n log n).
  • Worrying about floors and ceilings.
    • T(⌊n/2⌋) and T(⌈n/2⌉) give the same asymptotic answer as T(n/2). You can safely ignore them.
  • Assuming the theorem covers every divide-and-conquer algorithm.
    • Quick sort's worst case is T(n) = T(n − 1) + n, and its average case involves unequal splits. Neither is a Master Theorem problem.

Frequently Asked Questions

What is the Master Theorem used for?

It gives the asymptotic running time of a divide-and-conquer algorithm directly from its recurrence, without expanding a recursion tree or guessing and proving a bound. If a recurrence has the form T(n) = aT(n/b) + f(n) with constant a ≥ 1 and b > 1, you compare f(n) against n^(log_b a) and read the answer off one of three cases.

What do a, b and f(n) mean?

a is the number of subproblems each call creates, b is the factor by which the input size shrinks in each subproblem, and f(n) is the work done outside the recursive calls — the dividing and combining. For merge sort, a = 2, b = 2 and f(n) = n, because it makes two half-size calls and merges in linear time.

Why does merge sort come out as Θ(n log n)?

For merge sort, n^(log_b a) = n^(log₂2) = n, and f(n) = n as well. Since they match, Case 2 applies and the answer is Θ(n log n). Intuitively, each level of the recursion tree costs a total of n, and there are log₂n levels.

When does the Master Theorem not apply?

When a or b is not constant, when a < 1 or b ≤ 1, when the subproblems have different sizes, when the input shrinks by subtraction rather than division, when f(n) differs from n^(log_b a) by less than a polynomial factor, or when the Case 3 regularity condition fails. In all of those situations use a recursion tree or the substitution method.

What is the regularity condition and why does it exist?

It requires that a·f(n/b) ≤ c·f(n) for some constant c < 1 and all sufficiently large n. It says the work at each level really does shrink geometrically as you go down the tree, which is what lets the root's cost dominate the total. It holds automatically for polynomial f, so it usually needs no more than a line to check.

What is the difference between the Master Theorem and the recursion tree method?

The Master Theorem is a shortcut — three cases, no work, but it only fits recurrences of one exact shape. The recursion tree method is a general technique that works on any recurrence, including unequal splits and subtractive ones, at the cost of doing the summation yourself. The Master Theorem is really just the recursion tree argument, solved once in general.

Key Takeaways

  • The Master Theorem solves T(n) = aT(n/b) + f(n) for constant a ≥ 1 and b > 1.
  • Everything hinges on comparing f(n) with the watershed function n^(log_b a).
  • Case 1: leaves dominate → Θ(n^(log_b a)). Case 2: levels tie → Θ(n^(log_b a) log n). Case 3: root dominates → Θ(f(n)).
  • The difference in Cases 1 and 3 must be polynomial; a log factor is not enough, and that gap is where the theorem stays silent.
  • Case 3 is not finished until you have checked the regularity condition a·f(n/b) ≤ c·f(n).
  • Unequal splits, subtractive recurrences and non-constant a all fall outside the theorem — reach for a recursion tree instead.

From Code to Recurrence to Solution

// Reading a recurrence off the code, then solving it with the Master Theorem.

// T(n) = 2T(n/2) + n
//   a = 2 (two recursive calls)
//   b = 2 (each on half the input)
//   f(n) = n (the merge)
// Watershed: n^(log2 2) = n. f(n) = n matches it -> CASE 2.
// T(n) = Theta(n log n)
function mergeSort(arr) {
  if (arr.length <= 1) return arr;

  const mid = Math.floor(arr.length / 2);
  const left = mergeSort(arr.slice(0, mid));
  const right = mergeSort(arr.slice(mid));
  return merge(left, right);
}

function merge(left, right) {
  const out = [];
  let i = 0, j = 0;
  while (i < left.length && j < right.length) {
    out.push(left[i] <= right[j] ? left[i++] : right[j++]);
  }
  while (i < left.length) out.push(left[i++]);
  while (j < right.length) out.push(right[j++]);
  return out;
}

// T(n) = T(n/2) + 1
//   a = 1, b = 2, f(n) = 1
// Watershed: n^(log2 1) = n^0 = 1. f(n) = 1 matches it -> CASE 2.
// T(n) = Theta(log n)
function binarySearch(arr, target, low = 0, high = arr.length - 1) {
  if (low > high) return -1;

  const mid = Math.floor((low + high) / 2);
  if (arr[mid] === target) return mid;

  return arr[mid] < target
    ? binarySearch(arr, target, mid + 1, high)
    : binarySearch(arr, target, low, mid - 1);
}

// T(n) = 4T(n/2) + n
//   Watershed: n^(log2 4) = n^2, and f(n) = n is polynomially smaller
//   -> CASE 1. T(n) = Theta(n^2)
function fourWay(n) {
  if (n <= 1) return 1;

  let work = 0;
  for (let i = 0; i < n; i++) work++;          // f(n) = n

  return work + fourWay(n / 2) + fourWay(n / 2)
              + fourWay(n / 2) + fourWay(n / 2); // a = 4
}

// A calculator for the three cases.
// Compares f(n) = n^fExp against the watershed n^(log_b a).
function masterTheorem(a, b, fExp) {
  const watershed = Math.log(a) / Math.log(b);  // log_b(a)
  const EPS = 1e-9;

  if (fExp < watershed - EPS) {
    return `Case 1: T(n) = Theta(n^${watershed.toFixed(3)})`;
  }
  if (Math.abs(fExp - watershed) <= EPS) {
    return `Case 2: T(n) = Theta(n^${watershed.toFixed(3)} log n)`;
  }
  return `Case 3: T(n) = Theta(n^${fExp}) — verify a*f(n/b) <= c*f(n)`;
}

console.log(masterTheorem(2, 2, 1));  // Case 2: Theta(n log n)
console.log(masterTheorem(4, 2, 1));  // Case 1: Theta(n^2)
console.log(masterTheorem(2, 2, 2));  // Case 3: Theta(n^2)
console.log(masterTheorem(7, 2, 2));  // Case 1: Theta(n^2.807) - Strassen