Algorithms

Greedy Algorithm

Always pick the locally best option — and prove it works globally

A greedy algorithm builds a solution piece by piece, always making the choice that looks best at the moment. It's the simplest design strategy — when it works, it's typically faster and uses less memory than DP. When it doesn't work, it produces wrong answers silently. The hard part is proving it works.

  • TimeOften O(n log n) — sort + linear scan
  • SpaceOften O(1) extra
  • Correctness requiresGreedy choice property + optimal substructure
  • Vs DPGreedy commits; DP considers all options
  • When greedy failsCoin change with arbitrary denominations
  • When greedy winsMST, Huffman, scheduling, Dijkstra

Interactive visualization

Press play, or step through manually. The visualization is yours to drive — try it before reading on.

Open visualization fullscreen ↗

Watch the 60-second explainer

A condensed visual walkthrough — narrated, captioned, under a minute.

How greedy algorithms work

A greedy algorithm has three components:

  1. A choice function — given the current state, what's the locally best next step?
  2. A feasibility check — does this choice keep the problem solvable?
  3. A termination condition — when have we built a complete solution?

The pattern is "while the problem isn't solved: make the greedy choice, commit, repeat." No backtracking, no reconsidering. The simplicity is what makes greedy fast — and what makes it dangerous when the greedy choice property doesn't hold.

When greedy works (and when it fails)

Three problems, three different stories:

ProblemGreedy strategyOptimal?Why
Coin change (US coins: 1, 5, 10, 25)Largest coin ≤ remainingYesCoin denominations have the canonical property that no smaller-coin combo beats the larger one
Coin change (arbitrary: 1, 3, 4)Largest coin ≤ remainingNoGreedy on 6 picks [4, 1, 1]; DP optimal is [3, 3]
Activity selectionEarliest end time firstYesExchange argument: any optimal can be transformed to greedy without loss
0/1 knapsackBest value/weight ratioNoDiscrete items: greedy may pick one big item when two smaller ones would have higher value
Fractional knapsackBest value/weight ratioYesFractions allow optimal trade-offs at the boundary
Minimum spanning treeSmallest non-cycling edgeYesCut property of MSTs — proven via exchange
Huffman codingMerge two lowest-frequency nodesYesStandard exchange argument on optimal prefix codes
Longest path in general graphVariousNoNP-hard — greedy can't even approximate well

If you can't prove the greedy choice property, fall back to DP. If your greedy passes a few hand-picked examples but you're not sure why, build adversarial test cases — coin change (1, 3, 4) on 6 is the canonical adversary that breaks naive greedy thinking.

Greedy vs dynamic programming

GreedyDynamic programming
Decision policyLocally optimal — commitConsider all options — pick best after
TimeO(n) or O(n log n) typicalO(n²) or O(n × k) typical
SpaceO(1) typicalO(n) or O(n × k) for the table
BacktrackingNeverImplicit — the table considers all paths
Required propertyGreedy choice propertyOverlapping subproblems
Failure modeWrong answer, silentlySlower, may be unnecessary
Always correct?Only if greedy choice property holdsYes (when properties match)

Try greedy first — if you can prove correctness, the resulting algorithm is simpler and faster. If you can't prove it, or you find a counter-example, fall back to DP.

Famous greedy algorithms

Activity Selection

Given n activities with start and end times, find the maximum non-overlapping subset.

function activitySelection(activities) {
  // Sort by end time
  activities.sort((a, b) => a.end - b.end);
  const selected = [activities[0]];
  let lastEnd = activities[0].end;
  for (let i = 1; i < activities.length; i++) {
    if (activities[i].start >= lastEnd) {
      selected.push(activities[i]);
      lastEnd = activities[i].end;
    }
  }
  return selected;
}

O(n log n) for the sort, O(n) for the scan. The proof: any optimal solution either includes the earliest-ending activity, or it can be modified to include it without losing count — exchange argument.

Huffman Coding

Build a prefix code where common characters get short codes and rare ones get long codes. Greedy strategy: repeatedly merge the two lowest-frequency nodes into a new node whose frequency is their sum.

function huffmanTree(freqs) {
  // freqs: Map of character -> frequency
  const heap = new MinHeap();
  for (const [char, freq] of freqs) heap.push({ char, freq, left: null, right: null });

  while (heap.size() > 1) {
    const a = heap.pop();
    const b = heap.pop();
    heap.push({ char: null, freq: a.freq + b.freq, left: a, right: b });
  }
  return heap.pop(); // root of the Huffman tree
}

Used in DEFLATE (zip, gzip), JPEG, MP3. The greedy choice — merge lowest two — provably builds an optimal prefix code by exchange argument.

Fractional Knapsack

function fractionalKnapsack(items, capacity) {
  // items: [{value, weight}]
  items.sort((a, b) => (b.value / b.weight) - (a.value / a.weight));
  let total = 0;
  for (const item of items) {
    if (capacity >= item.weight) {
      total += item.value;
      capacity -= item.weight;
    } else {
      total += item.value * (capacity / item.weight);
      break;
    }
  }
  return total;
}

When to reach for greedy

  • Optimization problems with a clear "best" local choice. Activity selection, fractional knapsack, MST, shortest path with non-negative weights.
  • Approximation algorithms for NP-hard problems. Greedy set cover gives an O(log n)-approximation, the best polynomial-time achievable. Greedy max-cut gives a 1/2-approximation.
  • Online algorithms. When you must decide now without seeing future inputs (cache eviction, online scheduling), greedy choices are forced — no backtracking is even possible.
  • Constructive heuristics. When optimal isn't required but a good answer fast matters — packing, bin packing, route construction in vehicle routing.

How to prove a greedy works

  1. Identify the greedy choice. What's the locally best step?
  2. State the greedy choice property. "Some optimal solution makes this greedy choice first."
  3. Prove it via exchange. Assume an optimal solution differs from greedy. Show you can swap one element of the optimal for the greedy choice without making it worse. By induction, greedy = optimal.
  4. State the optimal substructure. After making the greedy choice, what remains is a smaller version of the same problem.
  5. Recurse. The recursion gives an inductive proof that greedy on the whole problem is optimal.

If any step gets stuck, your greedy may not be correct. Construct an adversarial input — coin change (1, 3, 4) on 6 is a great template — to verify.

Common bugs and edge cases

  • Confusing "intuitively right" with "provably correct." Greedy strategies that feel right often fail on adversarial inputs. Always test edge cases.
  • Sorting by the wrong key. Activity selection sorted by start time gives wrong answers. Sort by end time. Knapsack sorted by absolute value (not value/weight ratio) is also wrong.
  • Ignoring the feasibility check. The greedy choice must keep the remaining problem solvable. For activity selection: only pick if it doesn't overlap with prior picks. Skipping the check produces invalid solutions.
  • Trying greedy on 0/1 knapsack. Doesn't work — needs DP. Easy to confuse with fractional knapsack which IS greedy. Discrete vs continuous is the distinguishing feature.
  • Assuming greedy is always faster than DP. Greedy with a sort is O(n log n); some DP solutions are O(n). Profile before assuming.

Frequently asked questions

When does a greedy algorithm work?

Two properties must hold. (1) Greedy choice property — a globally optimal solution can be reached by making locally optimal choices, never reconsidering. (2) Optimal substructure — after the greedy choice, the remaining subproblem has the same structure and can be solved greedily again. Proving both is harder than running the algorithm.

When does greedy NOT work?

The classic failure: coin change with arbitrary denominations. For US coins (1, 5, 10, 25), greedy works — always pick the largest coin ≤ remaining amount. For coins (1, 3, 4) making 6, greedy gives [4, 1, 1] (3 coins); optimal is [3, 3] (2 coins). The greedy choice (4) eliminated a better solution. Use DP when the greedy choice property doesn't hold.

How is greedy different from dynamic programming?

Both build solutions from subproblems. DP considers ALL choices at each step and picks the best (after solving each subsubproblem). Greedy commits to one choice immediately and never revisits. Greedy is faster but only correct when the locally best choice always leads to the globally best solution.

What's the exchange argument?

The standard proof technique for greedy correctness. Assume an optimal solution differs from the greedy one. Show that you can "exchange" parts of the optimal for the greedy choice without making it worse. Repeat until the optimal solution becomes the greedy one — proving the greedy is also optimal. Used to prove activity selection, Huffman, fractional knapsack, MST algorithms.

Why is fractional knapsack greedy but 0/1 isn't?

Fractional knapsack lets you take fractions of items. Sort by value/weight ratio and take items greedily — fill up to capacity, taking a fraction of the last item. The greedy choice property holds because you can always trade a worse item's fraction for a better one's. 0/1 knapsack forces all-or-nothing; the greedy choice may force taking a low-value item when leaving room for two better-value items would be better. Needs DP.

What's the activity selection problem?

Given activities with start and end times, find the maximum number of non-overlapping activities. Greedy strategy: sort by end time, then iterate picking each activity that doesn't overlap with the previously picked one. Optimal in O(n log n). The greedy choice property holds: picking the earliest-ending activity always leaves the most room for future activities.

Is Dijkstra a greedy algorithm?

Yes — it's the canonical greedy graph algorithm. At each step, it picks the unvisited node with the smallest tentative distance and locks it in (greedy choice). The proof relies on non-negative edge weights ensuring no later choice can improve a previously committed distance — that's the greedy choice property in action.