Greedy Algorithms
Build a solution by repeatedly taking the locally best choice — correct only when an exchange argument proves that choice never hurts.
Overview
A greedy algorithm makes one irrevocable decision at a time using a simple rule ("earliest finishing interval", "cheapest edge", "highest value per weight") and never reconsiders it. When it works it is usually the fastest possible approach — typically a sort followed by a single sweep, O(n log n).
The catch is that greedy is a proof obligation, not a technique: the same local rule that solves Activity Selection optimally gives wrong answers for weighted intervals or 0/1 Knapsack. Before trusting a greedy solution you must argue that a locally best choice can always be extended to a globally optimal one.
Intuition
A mental model before the formal terms.
Imagine paying 63 cents with coins 25, 10, 5, 1: take the biggest coin that fits, repeat. It works because every coin is a multiple of the next smaller one, so a larger coin can always replace a group of smaller ones without loss. With coins {1, 3, 4} and amount 6 the same rule takes 4 + 1 + 1 (three coins) while 3 + 3 (two coins) is optimal — the structure that made greedy safe is gone.
How it works
- Identify the greedy choice: a rule that picks one element using only local information (usually after sorting by some key).
- Show the greedy-choice property: some optimal solution contains that first choice. The standard tool is an exchange argument — take any optimal solution, swap in the greedy choice, and show nothing gets worse.
- Show optimal substructure: after the choice, what remains is a smaller instance of the same problem, so induction finishes the proof.
- Implement as sort + sweep, or with a Priority Queue when the "best remaining" element changes dynamically (Huffman, Dijkstra, Prim).
Why it works
The exchange argument is the whole story. For activity selection: let g be the interval that finishes earliest and o the first interval in some optimal schedule. Since g finishes no later than o, replacing o with g keeps the schedule feasible and the same size — so an optimal schedule starting with g exists, and the remaining problem is the same problem on intervals starting after g ends.
Formally, problems where greedy is always optimal are exactly the matroids (and their generalizations); MST is the canonical example. You do not need the theory in interviews, but you do need a concrete exchange argument for your specific rule.
Recognition
How to tell a problem wants this.
- The statement asks for a maximum count or minimum cost with a natural ordering key (finish time, deadline, ratio, weight).
- Choices do not interact except through a simple resource (time, capacity, one machine): "schedule as many as possible", "minimum number of intervals to remove", "can you reach the end".
- A small example where taking the obvious best choice first is provably fine — and no counterexample after trying to break it.
- Constraints of
n ≤ 10^5with a single sort suggestingO(n log n).
Interactive visualization
Play, step, change the input. ← → and space work too.
No interactive visualization for this topic yet
Related visualizations are linked under Related.
Pseudocode
1sort items by the greedy key2solution = []3for item in items:4 if item is compatible with solution:5 solution.add(item) # never undone6return solutionImplementations
1import math2 3# Greedy: build the answer by repeatedly taking the locally best option.4# This is only correct when an EXCHANGE ARGUMENT holds — that swapping any5# optimal solution toward the greedy choice never makes it worse. The two6# functions below are the same shape; only one of them is correct.7 8 91 · Correct greedy: coin change with a canonical system (1, 5, 10, 25)10def coins_canonical(amount: int, coins: list[int]) -> int:11 count = 012 rest = amount13 for c in coins: # coins sorted descending14 q, rest = divmod(rest, c)15 count += q16 return count if rest == 0 else -117 18 192 · The same greedy is WRONG for a non-canonical system like {1, 3, 4}:20# greedy gives 6 = 4+1+1 (3 coins), the optimum is 3+3 (2 coins)21def coins_dp(amount: int, coins: list[int]) -> int:22 dp = [math.inf] * (amount + 1)23 dp[0] = 024 for a in range(1, amount + 1):25 for c in coins:26 if c <= a and dp[a - c] + 1 < dp[a]:27 dp[a] = dp[a - c] + 128 return -1 if dp[amount] == math.inf else int(dp[amount])29 30 313 · A greedy that IS provable: to cover points with unit intervals, always32# place the interval starting at the leftmost uncovered point33def min_unit_intervals(points: list[int], width: int) -> int:34 ordered = sorted(points)35 used = 036 i = 037 while i < len(ordered):38 used += 139 end = ordered[i] + width # the interval [ordered[i], ordered[i]+width]40 while i < len(ordered) and ordered[i] <= end:41 i += 142 return used43 44 454 · The exchange argument for it: any optimal cover can be rewritten to46# start its leftmost interval at the leftmost uncovered point without using47# more intervals, because that placement covers a superset of what any48# interval covering that point could cover to its right.49 50 515 · The practical test: sort by some key, take greedily, and CHECK against52# brute force on small inputs before trusting it53def greedy_matches_optimal(amount: int, coins_desc: list[int]) -> bool:54 return all(coins_canonical(a, coins_desc) == coins_dp(a, coins_desc) for a in range(amount + 1))divmod(rest, c)returns quotient and remainder in one call, which is both faster and clearer than computing them separately.sorted(points)returns a new list, so the function is side-effect free by default — the opposite oflist.sort().math.infas the DP sentinel needs no overflow guard; the explicitdp[a - c] + 1 < dp[a]comparison also avoids amin()call per candidate.- The
all(... for a in range(...))generator ingreedy_matches_optimalshort-circuits on the first mismatch. int(dp[amount])converts back from the floatmath.infdomain, which is the small cost of usingmath.infin an integer table.
divmod(a, b)is a single call returning both results and is the idiomatic spelling for this pattern.sorted()copies whilelist.sort()mutates — choosing the former is what makesmin_unit_intervalsfree of side effects.math.infis a float, so an integer DP table becomes float-typed; a large integer sentinel keeps itlist[int].hypothesisis the natural tool for the verify-against-brute-force habit this entry advocates, generating small random inputs automatically.
- Using
list.sort()and mutating the caller's list whensorted()was intended. - Trusting a greedy without the cross-check, which is the entire point of the entry.
- Leaving
math.infin a table that downstream code expects to be integers.
- JavaScript is the only language whose default sort actively breaks greedy algorithms: without a comparator it orders numbers lexicographically, so a "sort then take greedily" solution silently takes the wrong things.
- Integer division: Python
divmodgives both results in one call, C++/and%on ints already truncate, and JS/TS needMath.flooraround a float division. - Sentinels:
Infinityandmath.infsaturate safely; C++INT_MAXrequires an explicit reachability guard before any addition. - Sorting side effects: Python
sorted()copies by default andlist.sort()mutates; C++std::sortand JS/TSsortalways mutate, so a copy must be made deliberately.
Complexity
Dominated by the sort; O(n log n) with a heap when the best remaining choice changes dynamically.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- You can state and defend an exchange argument for the greedy choice.
- Interval scheduling, interval partitioning, deadline scheduling, Huffman coding, MST, Dijkstra, fractional knapsack, jump game, gas station.
- The DP formulation exists but the optimal transition is always the same "obvious" one — then greedy is the DP with the search removed.
- 0/1 knapsack: items (weight, value) = (10, 60), (20, 100), (30, 120) with capacity 50 — greedy by ratio takes 160, optimal is 220.
- Coin change with arbitrary denominations ({1, 3, 4}, amount 6).
- Weighted interval scheduling, longest path, or any problem where an early choice constrains later ones in a non-local way — use Dynamic Programming.
- When you cannot find the exchange argument. A greedy that "seems to work on examples" is the most common wrong answer in interviews.
Alternatives
Common mistakes
- Choosing the wrong greedy key (sorting intervals by start time instead of finish time).
- Skipping the proof and shipping a greedy that fails on a hidden case.
- Confusing greedy with DP: DP explores all transitions and keeps the best; greedy commits to one.
- Forgetting ties: several keys equal — pick the tie-break that keeps the exchange argument valid.
Interview patterns
- Sort by finish time, sweep, count compatible intervals.
- Sort by deadline / by ratio, then use a heap to undo the worst earlier choice (job sequencing, "IPO", "maximum performance of a team").
- Farthest-reach greedy: jump game, minimum jumps, video stitching.
- Two-phase "prove greedy then implement": state the exchange argument aloud before coding.
- Merge IntervalsIntermediate