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.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
How greedy algorithms work
A greedy algorithm has three components:
- A choice function — given the current state, what's the locally best next step?
- A feasibility check — does this choice keep the problem solvable?
- 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:
| Problem | Greedy strategy | Optimal? | Why |
|---|---|---|---|
| Coin change (US coins: 1, 5, 10, 25) | Largest coin ≤ remaining | Yes | Coin denominations have the canonical property that no smaller-coin combo beats the larger one |
| Coin change (arbitrary: 1, 3, 4) | Largest coin ≤ remaining | No | Greedy on 6 picks [4, 1, 1]; DP optimal is [3, 3] |
| Activity selection | Earliest end time first | Yes | Exchange argument: any optimal can be transformed to greedy without loss |
| 0/1 knapsack | Best value/weight ratio | No | Discrete items: greedy may pick one big item when two smaller ones would have higher value |
| Fractional knapsack | Best value/weight ratio | Yes | Fractions allow optimal trade-offs at the boundary |
| Minimum spanning tree | Smallest non-cycling edge | Yes | Cut property of MSTs — proven via exchange |
| Huffman coding | Merge two lowest-frequency nodes | Yes | Standard exchange argument on optimal prefix codes |
| Longest path in general graph | Various | No | NP-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
| Greedy | Dynamic programming | |
|---|---|---|
| Decision policy | Locally optimal — commit | Consider all options — pick best after |
| Time | O(n) or O(n log n) typical | O(n²) or O(n × k) typical |
| Space | O(1) typical | O(n) or O(n × k) for the table |
| Backtracking | Never | Implicit — the table considers all paths |
| Required property | Greedy choice property | Overlapping subproblems |
| Failure mode | Wrong answer, silently | Slower, may be unnecessary |
| Always correct? | Only if greedy choice property holds | Yes (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
- Identify the greedy choice. What's the locally best step?
- State the greedy choice property. "Some optimal solution makes this greedy choice first."
- 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.
- State the optimal substructure. After making the greedy choice, what remains is a smaller version of the same problem.
- 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.