JAVASCRIPT / CHAPTER 16

Recursive decomposition and search invariants

Advanced

Prove progress before trusting a recursive algorithm.

By the end of this chapter

  • Identify base cases
  • Track half-open search bounds
  • Compare time and stack costs

The idea

A recursive function solves a smaller problem and combines its result. Specify a base case and a measure that decreases on every recursive path. Tracing only a successful middle-sized example can miss empty input or a branch that repeats the same state. Recursion also uses stack space; an iterative implementation may be more appropriate for very deep inputs.

Binary search illustrates a stronger invariant: in a sorted array, any remaining candidate lies inside a half-open interval [lo, hi). If the midpoint is too small, moving lo to mid plus one removes it; otherwise setting hi to mid keeps the possible first match. The interval length shrinks. The function below returns an insertion position, so reaching the array length is a meaningful result rather than an invalid index to dereference.

JavaScript example
function lowerBound(a, x, lo = 0, hi = a.length) {
  if (lo === hi) return lo;
  const mid = lo + Math.floor((hi - lo) / 2);
  return a[mid] < x ? lowerBound(a, x, mid + 1, hi)
                    : lowerBound(a, x, lo, mid);
}
console.log(lowerBound([2, 4, 4, 9], 4));
Result
1

The equality branch retains the midpoint and searches for an earlier equal value.

Try it yourself

Implement an iterative equivalent and test empty input, duplicates, and a target beyond every element.

Use your own editor for this exercise. Runnable challenges on CodeQuest are available for JavaScript, Python, and SQL.

Open JavaScript coding challenges

CHECK YOUR UNDERSTANDING

Chapter checkpoint

Which quantity strictly decreases on every non-base recursive call shown above?