Greedy Algorithm — Why Earliest Start Time Causes Timeouts
Payment jobs timed out hourly: greedy by earliest start time blocked shorter jobs.
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
- Greedy algorithms make the locally optimal choice at each step, hoping it leads to a globally optimal solution.
- Works when the problem has the greedy choice property: a local optimum is also a global optimum.
- Classic examples: interval scheduling (earliest finish time), Dijkstra's algorithm, Huffman coding.
- Performance: usually O(N log N) or O(N), much faster than DP's O(N²) or higher.
- Fails when a locally suboptimal choice is needed to reach the global optimum — coin change with arbitrary denominations is the classic trap.
Imagine you are at a buffet with a small plate. A 'Greedy' approach means you walk down the line and take the biggest, most delicious item you see at every single station without looking ahead. You don't think, 'If I skip this pasta, I'll have room for the steak later.' You just take the best thing in front of you right now. In programming, this works perfectly for some problems (like making change with standard coins) but fails for others where a small sacrifice now leads to a massive win later.
How Greedy Algorithms Actually Work — And Why They Fail
A greedy algorithm builds a solution step by step, making the locally optimal choice at each decision point with the hope that these local choices lead to a globally optimal solution. The core mechanic is simple: pick the best immediate option, commit to it, and never revisit past decisions. This is fundamentally different from dynamic programming, which explores multiple branches and backtracks.
In practice, greedy algorithms work when the problem exhibits optimal substructure and the greedy-choice property — meaning a locally optimal decision is also part of some globally optimal solution. For example, Dijkstra's shortest path algorithm works because the shortest path to a node can be built from the shortest path to its predecessor. But if you apply greedy selection to the interval scheduling problem using earliest start time, you get suboptimal results: picking the earliest start can block later intervals that would yield more total selections. The failure mode is silent — the algorithm returns a valid schedule, just not the maximum one.
Use greedy algorithms when you can prove the greedy-choice property holds, or when an approximate solution is acceptable and speed matters. In real systems, greedy approaches power Huffman coding (O(n log n)), Prim's MST (O(E log V)), and load balancers that assign tasks to the least-loaded server. The key insight: greedy is fast (often O(n log n) or better), but correctness requires proof, not intuition.
Advantages and Disadvantages of Greedy Algorithms
Greedy algorithms are beloved for their simplicity and speed, but they come with trade-offs that every developer must understand before deploying them in production.
Advantages: - Speed: Typically O(N log N) or O(N) — far faster than DP or backtracking. - Simplicity: Easy to implement, test, and reason about. - Low memory: Often require only O(1) or O(N) extra space beyond input. - Great for real-time systems: When decisions must be made instantly with limited data, greedy heuristics are the go-to.
Disadvantages: - Not always optimal: Without the greedy choice property, the solution may be far from optimal. - Hard to verify: Proving correctness requires the exchange argument or greedy-stays-ahead — often non-trivial. - Fragile under data changes: A modification to the problem constraints (e.g., new coin denominations) can silently break greedy. - No backtracking: Greedy commits early; if a mistake is made, there is no recovery. - Difficult debugging: Suboptimal results may appear only at scale or under specific inputs, making root cause analysis challenging.
Interval Scheduling — The Power of the Earliest Finish Time
The Interval Scheduling problem asks: 'Given a set of activities with start and end times, what is the maximum number of non-overlapping activities you can perform?'
Many beginners try to pick the shortest interval or the one that starts earliest, but those fail. The proven greedy choice is to always pick the activity that ends earliest. This leaves the maximum amount of time remaining for all subsequent activities.
Why does this work? Because picking the activity that finishes earliest maximizes the remaining time for the rest, and any optimal solution can be transformed step-by-step into the greedy one (exchange argument).
- Earliest finish leaves maximum contiguous time for later activities.
- Any optimal solution can be transformed to include the earliest-finishing activity without reducing count.
- The proof is a classic exchange argument — swap the first activity of an optimal solution with the greedy choice.
C++ Implementation of Interval Scheduling
The same logic can be implemented in C++ using the STL. The algorithm sorts intervals by end time, then iterates to select non-overlapping activities. Time complexity remains O(N log N). The C++ version is useful when integrating with legacy systems or when performance on very large datasets requires fine-grained memory control.
Coin Change — Understanding the Greedy Failure
For a 'canonical' coin system (like USD: 1, 5, 10, 25), the greedy approach of taking the largest coin first is mathematically optimal. However, if a country used coins of 1, 3, and 4 units, greedy would fail to find the best way to make 6 units. Greedy would take a 4, then two 1s (3 coins), whereas the optimal is two 3s (2 coins).
This happens because the greedy choice property breaks: taking the largest coin now prevents you from using a combination of medium coins that would be better later. The problem still has optimal substructure (DP works), but lacks the greedy choice property.
Longest Path: A Classic Greedy Failure
The longest path problem in a directed acyclic graph (DAG) asks for the path with the maximum total weight (or number of edges) from a source to a target. Greedy approaches — such as always picking the edge with the largest current weight — fail spectacularly because an early expensive edge can lead to a dead end, missing a longer overall path.
Consider a graph where from node A you can go to B (weight 10) or C (weight 9). B leads to D (weight 1) and then to target (weight 1), total = 12. C leads directly to target (weight 100), total = 109. Greedy picks B (10 > 9), then D, yielding only 12, while the optimal path (A→C→Target) gives 109. The greedy choice property does not hold because the immediate benefit of the high-weight edge blinds the algorithm to future opportunities.
This counterexample visually illustrates why greedy cannot be trusted for path-based problems unless the graph has special structure (e.g., Dijkstra works for shortest paths because edge weights are non-negative and the problem has the greedy choice property for shortest distances).
Huffman Coding — Greedy Data Compression
Huffman coding builds an optimal prefix-free binary code for a set of characters based on their frequencies. The greedy choice: merge the two least frequent characters at each step. This forms a binary tree where more frequent characters get shorter codes.
Why does this greedy work? Because merging the two smallest frequencies minimises the total weighted path length (the sum of frequency × depth). This is the 'optimal merge' pattern — repeatedly merging the two smallest items is a classic greedy that's provably optimal via the greedy choice property.
Proving Correctness of Greedy Algorithms
Greedy algorithms are fast but often wrong. You must prove correctness before relying on one in production. Two standard proof techniques exist:
- Exchange Argument: Assume an optimal solution exists that differs from the greedy solution. Show you can swap elements of the optimal with greedy choices without degrading the solution, proving greedy is also optimal.
- Greedy Stays Ahead: Show that at every step k, the greedy algorithm's partial solution is at least as good (or further along) as any other algorithm's partial solution. This is often used for scheduling problems.
These proofs require the problem to have optimal substructure — the optimal solution contains optimal solutions to subproblems — and the greedy choice property — a locally optimal choice is part of some globally optimal solution.
Environment Setup for Algorithm Analysis
To benchmark greedy algorithms against complex DP solutions, we use a containerized environment to ensure consistent CPU/Memory constraints for timing.
System.nanoTime() is not enough.Practice Problems to Master Greedy Algorithms
The best way to internalise when greedy works — and when it doesn't — is to solve a variety of problems. Below is a curated list of practice problems that test different greedy patterns. Work through them in order of increasing difficulty.
- Fractional Knapsack (GeeksforGeeks / LeetCode) — The classic greedy success story. Sort by value-to-weight ratio and take items fully or fractionally.
- Jump Game (LeetCode 55) — Determine if you can reach the last index. The greedy solution tracks the furthest reachable index.
- Jump Game II (LeetCode 45) — Minimum jumps to reach the end. Greedy BFS approach.
- Job Sequencing with Deadlines (GeeksforGeeks) — Maximise profit when each job has a deadline and profit. Sort by profit and slot jobs greedily.
- Minimum Number of Arrows to Burst Balloons (LeetCode 452) — Interval scheduling variant. Sort by end coordinate.
- Non-overlapping Intervals (LeetCode 435) — Find maximum number of non-overlapping intervals (or minimum removals). Same greedy as interval scheduling.
- Assign Cookies (LeetCode 455) — Simple greedy: sort children's greed and cookie sizes, assign smallest cookie that satisfies.
- Candy Distribution (LeetCode 135) — Two-pass greedy to ensure each child gets at least one candy and higher rating gets more.
Each of these problems reinforces a different aspect of the greedy choice property. Solve them and compare your greedy solution with a brute-force or DP approach for small inputs to build intuition.
Greedy vs Dynamic Programming: A Side-by-Side Comparison
Both greedy and dynamic programming are used for optimisation problems, but they differ fundamentally in how they explore the solution space. The table below summarises the key differences.
| Criterion | Greedy | Dynamic Programming |
|---|---|---|
| Approach | Makes a local optimal choice at each step, commits and never backtracks. | Solves subproblems recursively, stores results to avoid recomputation. |
| Correctness conditions | Requires both greedy choice property and optimal substructure. | Requires only optimal substructure (no greedy property needed). |
| Time complexity | Usually O(N log N) or O(N) | Typically O(N²) or O(N * W) |
| Space complexity | O(1) extra or O(N) for sorting | Often O(N * W) for tabulation |
| Backtracking | None | Yes — memoization or tabulation revisits subproblems. |
| Robustness | Fragile — a small change in problem constraints can break greedy. | Robust — handles a wide range of constraint variations. |
| Example problems | Interval scheduling, Huffman, Dijkstra, Fractional Knapsack | 0/1 Knapsack, Edit Distance, LCS, Weighted Interval Scheduling |
| Production risk | High if correctness proof is skipped | Low if subproblem space is manageable |
When to choose which? If the problem has the greedy choice property, use greedy for speed. If not, or if you're unsure, use DP. In production systems, a common pattern is to implement greedy as the default fast path, but fall back to DP when greedy produces suboptimal results (detected via monitoring).
Standard Greedy Algorithms That Won't Burn You
Not all greedy algorithms are traps. Some are canonical precisely because the locally optimal choice at every step provably leads to a globally optimal solution. You need to know which ones you can trust without a formal proof every damn time.
Fractional Knapsack is the poster child. You have items with weight and value, and you can take fractions of any item. Sorting by value-to-weight ratio and grabbing the highest first always yields the maximum total value. This isn't a heuristic — it's a theorem.
Dijkstra's algorithm for shortest paths on graphs with non-negative edge weights? Greedy. It picks the unvisited node with the smallest known distance and finalizes it. Works because any other path to that node would be longer by the time you find a shorter alternative. Kruskal's and Prim's for minimum spanning trees are also greedy — they repeatedly pick the smallest edge that doesn't create a cycle. Trust them for tree-building.
Huffman coding you already saw. These algorithms share a property called the greedy-choice property: an optimal solution can be built by making the best local decision at each step. No backtracking needed. No DP for special cases.
When you reach for a greedy solution, ask: does this problem have optimal substructure? If yes, you might have a winner. If not, you're writing buggy production code.
Approximate Greedy for NP-Complete — When Good Enough Is
Real life doesn't care about your P vs NP obsession. When you face an NP-complete problem like Set Cover or Vertex Cover, waiting for the optimal solution is a fool's errand. You need an approximation algorithm that runs in polynomial time. Greedy is your hammer here.
Set Cover is a classic: you have a universe of elements and a collection of subsets. Find the smallest number of subsets whose union equals the universe. The greedy approach: at each step, pick the subset covering the most uncovered elements. This yields an approximation ratio of ln(n) — meaning the answer is at worst ln(n) times the optimal size.
Is it perfect? Hell no. There are pathological cases where greedy performs poorly. But for most real-world inputs, it's fast and close enough. Production systems for ad targeting, network routing, and sensor placement use this exact trick. They don't wait for the optimal solution — they run greedy, get a decent answer in milliseconds, and move on.
The same greedy heuristic applies to Vertex Cover: pick an edge, add both endpoints to the cover, remove all incident edges, repeat. That gives a 2-approximation. Not great, not terrible. Acceptable when your cluster needs a solution before the next scheduling window closes.
Remember: when optimal is exponential, greedy gives you a polynomial-time approximation. Document the trade-off, set expectations with your stakeholders, and ship the fix.
Greedy Problems on Graphs — Not Just Shortest Paths
Graphs are a breeding ground for greedy algorithms beyond Dijkstra and MST. You'll encounter problems like scheduling on a single machine, finding a minimum vertex cover, or constructing a maximum matching. Each requires you to pick the locally optimal edge or node without much foresight.
Consider Minimum Spanning Tree (MST) — Kruskal's algorithm sorts edges by weight and adds them if they don't create a cycle. That greedy decision works because of the cut property: the lightest edge crossing any cut belongs to some MST. Prim's algorithm is greedy too: start from a node, always add the cheapest edge connecting the tree to a node outside it. Both run in O(E log V) with a priority queue.
Then there's Maximum Matching in bipartite graphs. Simple greedy: pick an edge whose endpoints are both unmatched, add it to the matching, repeat. This gives a maximal matching, not maximum — but it's a 2-approximation. In social network friend recommendations or ad assignment, that's often sufficient.
Another classic: Minimum number of platforms required for a train schedule. Sort arrival and departure times, then greedily assign platforms by counting trains currently at the station. It's a greedy sweep-line algorithm that runs in O(n log n).
The pattern? All these graph problems have a local criterion — smallest weight, earliest finish, cheapest edge — that you can evaluate independently. If the problem decomposes into independent decisions that don't interfere, greedy is your friend. If decisions cascade and interact, you're looking at DP or something worse.
The Database Scheduling That Caused Hourly Timeouts
- Earliest start time is a common wrong greedy choice for interval scheduling.
- Always verify the greedy choice property with a small counterexample before deployment.
- In production, test greedy correctness against brute-force for small random inputs.
print('Sorted intervals:', sorted(intervals, key=lambda x: x.end))Run brute-force with N<=10: max_activities_bruteforce(intervals)Key takeaways
Common mistakes to avoid
5 patternsUsing earliest start time for interval scheduling
Assuming greedy works for coin change with arbitrary denominations
Forgetting to prove greedy choice property before deployment
Using greedy for weighted interval scheduling
Applying greedy to the 0/1 Knapsack problem (non-fractional)
Practice These on LeetCode
Interview Questions on This Topic
What is the 'Greedy Choice Property' and how does it differ from 'Optimal Substructure'?
Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
That's Greedy & Backtracking. Mark it forged?
11 min read · try the examples if you haven't