Ternary Search

Two Midpoints on a Unimodal Function — Find the Extremum in O(log₃ n)

Ternary Search

Ternary search finds the minimum or maximum of a unimodal function by splitting the range into three parts with two midpoints and discarding one third on every iteration.

4 min read Level 3/5 #dsa#searching#ternary-search
What you'll learn
  • Define what makes a function unimodal and why binary search does not apply
  • Implement ternary search to find the extremum of a discrete unimodal sequence
  • Explain why ternary search rarely outperforms binary search in practice

Binary search requires a monotone function — values only go up, or only go down. Ternary search works on unimodal functions: values decrease to some peak (or valley) and then increase, with no second peak. The goal is to find that extremum.

Unimodal Functions

A function f is unimodal on [lo, hi] if there exists some peak p such that f is strictly increasing on [lo, p] and strictly decreasing on [p, hi] (or the mirror for a valley). Comparing f(m1) and f(m2) at two midpoints tells you which third of the range cannot contain the extremum.

Algorithm

Divide [lo, hi] into thirds with two midpoints m1 and m2:

  • If f(m1) < f(m2) — the peak is in [m1, hi], so set lo = m1 + 1.
  • If f(m1) > f(m2) — the peak is in [lo, m2], so set hi = m2 - 1.
  • If f(m1) === f(m2) — the peak is in [m1, m2]; shrink both ends.

Each iteration discards at least one third of the range.

// Find the index of the maximum in a unimodal array (discrete ternary search)
function ternarySearchMax(arr) {
  let lo = 0;
  let hi = arr.length - 1;

  while (hi - lo > 2) {
    const third = Math.floor((hi - lo) / 3);
    const m1 = lo + third;
    const m2 = hi - third;

    if (arr[m1] < arr[m2]) {
      lo = m1 + 1; // peak is to the right of m1
    } else if (arr[m1] > arr[m2]) {
      hi = m2 - 1; // peak is to the left of m2
    } else {
      lo = m1 + 1; // equal — safely shrink both sides
      hi = m2 - 1;
    }
  }

  // Scan the remaining tiny window (at most 3 elements)
  let maxIdx = lo;
  for (let i = lo + 1; i <= hi; i++) {
    if (arr[i] > arr[maxIdx]) maxIdx = i;
  }
  return maxIdx;
}

const values = [1, 3, 7, 12, 9, 5, 2];
ternarySearchMax(values); // 3  (arr[3] === 12)

For continuous functions (e.g. finding the minimum of a convex curve), run the loop a fixed number of iterations instead of until lo > hi:

function ternarySearchMin(f, lo, hi, iterations = 200) {
  for (let i = 0; i < iterations; i++) {
    const m1 = lo + (hi - lo) / 3;
    const m2 = hi - (hi - lo) / 3;
    if (f(m1) < f(m2)) hi = m2;
    else               lo = m1;
  }
  return (lo + hi) / 2; // approximate minimum x
}

// Example: minimum of f(x) = (x - 4)^2
const x = ternarySearchMin((x) => (x - 4) ** 2, 0, 10);
Math.round(x); // 4

Complexity

PropertyValue
IterationsO(log₃ n)
Comparisons per iteration2
Effective base-2 equivalentO(log n) with a larger constant

Why Ternary Search Rarely Beats Binary Search in Practice

Ternary search makes 2 comparisons per iteration to discard one third. Binary search makes 1 comparison per iteration to discard one half.

Discarding one third in 2 comparisons is the same asymptotic rate as binary search discarding one half in 1 comparison — both are O(log n). Ternary search’s constant factor of 2 means it is slower in practice for most discrete problems. It shines specifically on continuous unimodal functions where computing f is expensive and you want the fewest function evaluations (in which case each “comparison” is actually two separate evaluations of an expensive function, and the log₃ base matters).

Up Next

Rotated sorted arrays break the standard binary search invariant — but a modified version can still find targets in O(log n).

Search in a Rotated Sorted Array →