Greedy — The Complete Guide
Master greedy algorithms from first principles. Learn to recognize when a locally optimal choice leads to a globally optimal solution, build the intuition for proving correctness, and confidently solve interview questions that reward the greedy mindset.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
Some problems have a remarkable property: you can build the optimal solution one step at a time by always making the best choice available right now, without ever looking back or reconsidering. You never need to explore all possibilities. You never need to undo a decision. You just pick the locally best option at each step, and the sequence of local choices magically produces the globally best answer.
That's the greedy strategy. It works when the problem has two properties: greedy choice property (a locally optimal choice is part of some globally optimal solution) and optimal substructure (after making a greedy choice, the remaining problem is a smaller instance of the same problem). When both hold, greedy gives you the optimal answer in a fraction of the time that brute force or DP would take.
The catch? Greedy doesn't always work. If the locally best choice can lead to a globally worse outcome, you need DP or backtracking instead. The hardest part of greedy isn't coding — it's recognizing when it applies and convincing yourself (and the interviewer) that it's correct.
Analogies to Build Intuition
Making Change with Fewest Coins
You need to give 87 cents in change using the fewest coins. You grab the biggest coin that fits (quarter, quarter, quarter, dime, two pennies). You never reconsider — each choice is the locally best. This works for standard US coins because of their denominations. But for weird coin systems (like 1, 3, 4 cents), greedy fails — you'd need DP.
The Buffet Strategy
At an all-you-can-eat buffet with limited stomach space, you want maximum enjoyment. Greedy says: always pick the dish that gives you the most enjoyment per bite. If dishes are independent (eating one doesn't affect others), this works perfectly. But if eating dessert first ruins your appetite for steak, greedy fails — choices interact.
Scheduling Meetings
You have a room and many meeting requests. You want to fit the most meetings possible. Greedy says: always pick the meeting that ends earliest. This leaves the most room for future meetings. It works because finishing early never hurts — it only opens up more options.
What kind of problems does it solve?
Greedy is your go-to when:
- You need to maximize or minimize something by making sequential choices
- The problem involves scheduling, intervals, or deadlines
- You need to assign resources optimally (tasks to machines, items to containers)
- The problem asks for the minimum number of operations (jumps, coins, moves)
- A sorting step reveals the optimal processing order
- Making the locally best choice never hurts future options
The core insight
Greedy works when you can prove that being "selfish" at each step leads to the best overall outcome. The key question is: "Can making the locally best choice ever prevent me from reaching the globally best solution?" If the answer is no, greedy is correct. If the answer is yes (or you're not sure), you need DP.
Pattern Recognition
Recognizing when greedy works (and when it doesn't) is the hardest part. Here are the signals to look for and the traps to avoid.
Keywords & Signals in the Problem Statement
Use Greedy when you see...
- ✅"Maximum number of non-overlapping intervals"
- ✅"Minimum number of platforms / meeting rooms"
- ✅"Assign cookies / tasks to maximize satisfaction"
- ✅"Jump game" — can you reach the end?
- ✅"Minimum number of coins / jumps / operations"
- ✅"Partition labels" — split into maximum parts
- ✅"Gas station" — circular route feasibility
- ✅"Best time to buy and sell stock" (multiple transactions)
- ✅Sorting + processing in a specific order solves it
- ✅The problem has an exchange argument (swapping doesn't help)
Don't use Greedy when...
- ❌Choices interact — picking one item affects the value of others (use DP)
- ❌You need to consider ALL combinations — 0/1 Knapsack, subset sum (use DP)
- ❌The coin system is non-standard — greedy coin change fails for {1, 3, 4}
- ❌The problem asks for the NUMBER of ways (counting → DP)
- ❌You need the actual optimal subset, not just the value (often DP)
- ❌A small counterexample breaks the greedy choice
Greedy vs. Similar Patterns
| Pattern | When to Use | Key Difference |
|---|---|---|
| Greedy | Locally optimal choice is globally optimal; no need to reconsider | Makes one pass of irrevocable decisions; O(n) or O(n log n) time |
| Dynamic Programming | Overlapping subproblems; choices interact; need to try all options | Explores all subproblems; caches results; O(n²) or O(n × W) time |
| Backtracking | Need to generate all valid configurations; constraint satisfaction | Explores all branches; undoes choices; exponential time |
| Sorting + Two Pointers | Pair matching, partitioning after sorting | Sorting is a preprocessing step; greedy often sorts first too |
| Heap / Priority Queue | Need the current best element repeatedly | Often used as the data structure inside a greedy algorithm |
The greedy litmus test
Before committing to greedy, try to find a counterexample. Pick a small input where the greedy choice at step 1 leads to a worse overall result. If you can't find one after 2-3 attempts, greedy is likely correct. In interviews, say: "I believe greedy works here because [reason]. Let me verify with an example." Then trace through a case.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using a classic greedy problem: Activity Selection (Non-overlapping Intervals) — given a set of activities with start and end times, find the maximum number of non-overlapping activities you can attend.
Brute Force — Try All Subsets
Generate all possible subsets of activities. For each subset, check if all activities are non-overlapping. Track the largest valid subset. This is correct but exponentially slow — 2ⁿ subsets to check.
function maxActivitiesBrute(activities: number[][]): number { let maxCount = 0; // Generate all 2^n subsets for (let mask = 0; mask < (1 << activities.length); mask++) { const subset: number[][] = []; for (let i = 0; i < activities.length; i++) { if (mask & (1 << i)) subset.push(activities[i]); } // Check if all activities in subset are non-overlapping subset.sort((a, b) => a[0] - b[0]); let valid = true; for (let i = 1; i < subset.length; i++) { if (subset[i][0] < subset[i - 1][1]) { valid = false; break; } } if (valid) maxCount = Math.max(maxCount, subset.length); } return maxCount; } // Problem: O(2^n) subsets × O(n) validation each = way too slow. // Can we avoid exploring all subsets?
Optimal — Greedy: Sort by End Time, Pick Earliest Finish
The key insight: always pick the activity that finishes earliest. This leaves the maximum room for future activities. Sort by end time, then greedily select each activity that doesn't overlap with the last selected one.
function maxActivities(activities: number[][]): number { // Sort by end time (earliest finish first) activities.sort((a, b) => a[1] - b[1]); let count = 1; // always take the first activity let lastEnd = activities[0][1]; for (let i = 1; i < activities.length; i++) { if (activities[i][0] >= lastEnd) { // No overlap — take this activity count++; lastEnd = activities[i][1]; } // Overlap — skip this activity (greedy: don't take it) } return count; } // Sort: O(n log n). Scan: O(n). Total: O(n log n). // Why sort by END time? Because finishing early never hurts — // it only opens up more room for future activities. // This is the "exchange argument": swapping a later-ending activity // for an earlier-ending one can only help, never hurt.
Why Does the Greedy Choice Work?
The exchange argument
Suppose the optimal solution picks activity A that ends at time 10, but greedy picks activity B that ends at time 7. Can we swap A for B? Yes — B ends earlier, so everything that was compatible with A is also compatible with B (and possibly more). Swapping never makes things worse. This proves greedy is at least as good as optimal.
Earliest finish maximizes remaining time
By always picking the activity that finishes earliest, we maximize the time window available for future activities. Any other choice would end later, leaving less room. This is why sorting by end time is the correct greedy criterion.
The general principle
Greedy works when you can prove an 'exchange argument': any optimal solution can be transformed into the greedy solution without losing quality. If swapping the greedy choice into any optimal solution never makes it worse, greedy is correct.
The thinking process matters more than the code
In interviews, walk through this progression: "The brute force tries all 2ⁿ subsets. But I notice that picking the earliest-finishing activity never hurts — it only opens up more room. So I can sort by end time and greedily pick non-overlapping activities in one pass. O(n log n)." Then sketch the exchange argument to justify correctness.
Core Templates
Greedy problems don't have a single universal template like sliding window or binary search. Instead, they follow a few recurring strategies. The key is identifying which greedy criterion to use.
Template 1: Sort + Greedy Scan
Sort the input by the right criterion, then make a single pass picking the locally best option. This is the most common greedy template — used for interval scheduling, assignment problems, and many optimization tasks.
function greedySortAndScan(items: Item[]): Result { // 1. Sort by the greedy criterion // (end time, deadline, size, ratio — problem-specific) items.sort((a, b) => a.key - b.key); // 2. Greedy scan — process items in sorted order let result = initialValue; let state = initialState; // tracks what's been committed for (const item of items) { if (isCompatible(item, state)) { // Take this item (greedy choice) result = update(result, item); state = updateState(state, item); } // Skip incompatible items } return result; } // When to use: activity selection, non-overlapping intervals, // assign cookies, minimum arrows to burst balloons, // minimum number of platforms
Template 2: Greedy Expansion (Reach / Coverage)
Track the farthest point you can reach from the current position. Expand greedily by always extending your reach as far as possible. Used for jump games, interval coverage, and gas station problems.
function greedyExpansion(nums: number[]): Result { let currentEnd = 0; // farthest we can reach with current jumps let farthest = 0; // farthest we can reach overall let jumps = 0; // number of jumps taken for (let i = 0; i < nums.length - 1; i++) { // Update the farthest reachable position farthest = Math.max(farthest, i + nums[i]); // When we've exhausted the current jump's range if (i === currentEnd) { jumps++; currentEnd = farthest; // Early exit if we can already reach the end if (currentEnd >= nums.length - 1) break; } } return jumps; } // When to use: Jump Game, Jump Game II, Video Stitching, // minimum number of taps to water a garden
Template 3: Greedy from Both Ends
Process the input from both ends (or from the extremes), making the greedy choice at each step. Used when the optimal strategy involves balancing two competing priorities.
function greedyBothEnds(items: Item[]): Result { // Sort by one criterion (e.g., height descending, then k ascending) items.sort((a, b) => { if (a.primary !== b.primary) return b.primary - a.primary; return a.secondary - b.secondary; }); // Greedy insertion — place each item at its correct position const result: Item[] = []; for (const item of items) { result.splice(item.position, 0, item); } return result; } // When to use: Queue Reconstruction by Height, // Candy distribution, boats to save people
Template 4: Greedy Accumulation (Running State)
Maintain a running state (sum, balance, surplus) as you scan. Make greedy decisions based on the current state. Used for stock trading, gas station, and parentheses problems.
function greedyAccumulation(nums: number[]): Result { let result = 0; let runningState = 0; for (let i = 0; i < nums.length; i++) { // Update running state runningState += contribution(nums[i]); // Greedy decision based on current state if (runningState > 0) { result += runningState; // take the profit runningState = 0; // reset } // Or: if runningState < 0, reset to 0 (Kadane's-style) } return result; } // When to use: Best Time to Buy and Sell Stock II, // Gas Station, Maximum Subarray (Kadane's), // Partition Labels
Which template to pick?
Ask yourself: (1) Does sorting reveal the optimal order? → Template 1. (2) Am I expanding a reachable range? → Template 2. (3) Do I need to process from extremes or insert in order? → Template 3. (4) Am I tracking a running balance/surplus? → Template 4.
Variations & Sub-Patterns
Greedy isn't a single trick — it's a family of strategies united by the principle of local optimality. Here are the most common variations and how the greedy criterion changes for each.
| Variation | Greedy Criterion | Classic Example |
|---|---|---|
| Interval Scheduling | Sort by end time; pick earliest finish | Non-overlapping Intervals, Meeting Rooms |
| Interval Partitioning | Sort by start time; use heap for earliest-ending room | Meeting Rooms II, Minimum Platforms |
| Jump / Coverage | Track farthest reachable; jump when current range exhausted | Jump Game, Jump Game II, Video Stitching |
| Assignment / Matching | Sort both sides; match smallest available to smallest need | Assign Cookies, Boats to Save People |
| Stock Trading | Collect every upward price movement | Best Time to Buy and Sell Stock II |
| Digit / String Construction | Build result digit by digit using a stack; drop larger digits | Remove K Digits, Monotone Increasing Digits |
| Partition / Split | Track last occurrence; split when current index equals last occurrence | Partition Labels |
| Circular / Gas Station | Track running surplus; reset start when surplus goes negative | Gas Station, Circular Tour |
Problems mentioned above
Deep Dive: Jump Game II — Greedy Expansion
Given an array where nums[i] is the max jump length from position i, find the minimum number of jumps to reach the last index. The greedy insight: at each "level" (range of positions reachable with the current number of jumps), find the farthest you can reach. When you exhaust the current level, take a jump.
function jump(nums: number[]): number { let jumps = 0; let currentEnd = 0; // end of current jump's range let farthest = 0; // farthest reachable from current range for (let i = 0; i < nums.length - 1; i++) { farthest = Math.max(farthest, i + nums[i]); if (i === currentEnd) { // We've explored all positions in the current jump jumps++; currentEnd = farthest; } } return jumps; } // Time: O(n), Space: O(1) // Think of it as BFS by levels: each "jump" is a BFS level. // At each level, we find the farthest reachable position. // Greedy: always extend to the farthest point in the next level.
Deep Dive: Partition Labels — Last Occurrence Greedy
Given a string, partition it into as many parts as possible so that each letter appears in at most one part. The greedy insight: for each character, track its last occurrence. The current partition must extend at least to the last occurrence of every character it contains.
function partitionLabels(s: string): number[] { // Step 1: Record last occurrence of each character const lastIndex = new Map<string, number>(); for (let i = 0; i < s.length; i++) { lastIndex.set(s[i], i); } // Step 2: Greedy scan — extend partition to cover all characters const result: number[] = []; let partitionEnd = 0; let partitionStart = 0; for (let i = 0; i < s.length; i++) { // The partition must extend to at least the last occurrence // of the current character partitionEnd = Math.max(partitionEnd, lastIndex.get(s[i])!); if (i === partitionEnd) { // All characters in this partition are fully contained result.push(partitionEnd - partitionStart + 1); partitionStart = i + 1; } } return result; } // Time: O(n), Space: O(1) (at most 26 characters) // Greedy: extend the partition boundary to the farthest last-occurrence. // When the current index reaches the boundary, we can safely cut.
Deep Dive: Gas Station — Circular Surplus
Given gas stations in a circle with gas[i] fuel and cost[i] to reach the next station, find the starting station that lets you complete the circuit (or return -1). The greedy insight: if total gas ≥ total cost, a solution exists. Track the running surplus; whenever it goes negative, the start must be after the current station.
function canCompleteCircuit(gas: number[], cost: number[]): number { let totalSurplus = 0; let currentSurplus = 0; let start = 0; for (let i = 0; i < gas.length; i++) { const net = gas[i] - cost[i]; totalSurplus += net; currentSurplus += net; if (currentSurplus < 0) { // Can't start from any station before i+1 start = i + 1; currentSurplus = 0; } } // If total gas >= total cost, a solution exists and it's 'start' return totalSurplus >= 0 ? start : -1; } // Time: O(n), Space: O(1) // Greedy: if the running surplus goes negative at station i, // no station from 'start' to 'i' can be the answer. // Reset start to i+1 and continue. // If totalSurplus >= 0, the last 'start' is guaranteed to work.
Visual Walkthrough
Let's trace through three classic greedy problems step by step to see how the greedy choice plays out.
Activity Selection — Step by Step
Activities: [[1,4], [3,5], [0,6], [5,7], [3,9], [5,9], [6,10], [8,11], [8,12], [2,14], [12,16]] Step 1: Sort by end time → [[1,4], [3,5], [0,6], [5,7], [3,9], [5,9], [6,10], [8,11], [8,12], [2,14], [12,16]] Step 2: Greedy scan (always pick earliest finish that doesn't overlap) Take [1,4] → lastEnd = 4, count = 1 Skip [3,5] → 3 < 4 (overlaps) Skip [0,6] → 0 < 4 (overlaps) Take [5,7] → 5 ≥ 4 ✓ lastEnd = 7, count = 2 Skip [3,9] → 3 < 7 (overlaps) Skip [5,9] → 5 < 7 (overlaps) Skip [6,10] → 6 < 7 (overlaps) Take [8,11] → 8 ≥ 7 ✓ lastEnd = 11, count = 3 Skip [8,12] → 8 < 11 (overlaps) Skip [2,14] → 2 < 11 (overlaps) Take [12,16] → 12 ≥ 11 ✓ lastEnd = 16, count = 4 Answer: 4 activities — [1,4], [5,7], [8,11], [12,16] Key: Sorting by end time ensures we always leave maximum room for future activities.
Jump Game II — Step by Step
nums = [2, 3, 1, 1, 4] Goal: reach index 4 in minimum jumps jumps=0, currentEnd=0, farthest=0 i=0: farthest = max(0, 0+2) = 2 i === currentEnd (0===0) → jumps=1, currentEnd=2 "Jump 1 can reach indices 0-2" i=1: farthest = max(2, 1+3) = 4 i !== currentEnd (1≠2) i=2: farthest = max(4, 2+1) = 4 i === currentEnd (2===2) → jumps=2, currentEnd=4 "Jump 2 can reach indices 0-4" currentEnd(4) >= nums.length-1(4) → we can stop! Answer: 2 jumps Visualization: Index: 0 1 2 3 4 Value: 2 3 1 1 4 Jump 1: [0──────2] (from 0, reach up to index 2) Jump 2: [1──────────4] (from range [1,2], reach up to index 4) Key: Each "jump" is like a BFS level. We explore all positions in the current level and find the farthest reachable position for the next level.
Gas Station — Step by Step
gas = [1, 2, 3, 4, 5] cost = [3, 4, 5, 1, 2] net = [-2, -2, -2, 3, 3] (gas[i] - cost[i]) totalSurplus = -2 + -2 + -2 + 3 + 3 = 0 ≥ 0 → solution exists! Greedy scan: i=0: currentSurplus = -2 < 0 → start=1, reset to 0 i=1: currentSurplus = -2 < 0 → start=2, reset to 0 i=2: currentSurplus = -2 < 0 → start=3, reset to 0 i=3: currentSurplus = 3 ≥ 0 → keep going i=4: currentSurplus = 3+3 = 6 ≥ 0 → keep going Answer: start = 3 Verification: Starting at station 3: Station 3: tank = 0 + 4 - 1 = 3 Station 4: tank = 3 + 5 - 2 = 6 Station 0: tank = 6 + 1 - 3 = 4 Station 1: tank = 4 + 2 - 4 = 2 Station 2: tank = 2 + 3 - 5 = 0 ✓ (made it back!) Key: When surplus goes negative, no station in [start..i] can work. The deficit must be covered by surplus from later stations.
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different greedy strategy. Solve them in order.
LC 455. Assign Cookies
Why this pattern:
Sort + greedy matching (Template 1). Sort children by greed factor and cookies by size. Match the smallest cookie that satisfies each child. This is the simplest greedy problem — it builds the intuition that sorting reveals the optimal assignment order.
Key idea: Sort both arrays. Use two pointers: for each child (smallest greed first), find the smallest cookie that satisfies them. If the current cookie is too small, try the next cookie. If it fits, assign it and move to the next child. Greedy: always use the smallest sufficient cookie.
LC 122. Best Time to Buy and Sell Stock II
Why this pattern:
Greedy accumulation (Template 4). You can make unlimited transactions. The greedy insight: collect every upward price movement. If tomorrow's price is higher than today's, 'buy today, sell tomorrow.' This is equivalent to summing all positive price differences.
Key idea: Scan the prices. Whenever prices[i] > prices[i-1], add the difference to profit. This captures every upward movement. No need to track actual buy/sell points — just accumulate all gains. Greedy: never miss a profit opportunity.
LC 55. Jump Game
Why this pattern:
Greedy expansion (Template 2). Track the farthest index you can reach. If at any point your current index exceeds the farthest reachable, you're stuck. If farthest reaches or exceeds the last index, you can make it.
Key idea: Maintain 'farthest' = max reachable index. For each index i, update farthest = max(farthest, i + nums[i]). If i > farthest, return false (can't reach here). If farthest >= last index, return true. One pass, O(n).
LC 763. Partition Labels
Why this pattern:
Last-occurrence greedy (Template 4 variant). For each character, the partition must extend to its last occurrence. Track the partition boundary as the max last-occurrence of all characters seen so far. When the current index reaches the boundary, cut.
Key idea: First pass: record last index of each character. Second pass: extend partitionEnd = max(partitionEnd, lastIndex[s[i]]). When i === partitionEnd, the partition is complete — record its size and start a new one.
LC 435. Non-overlapping Intervals
Why this pattern:
Interval scheduling (Template 1). Sort by end time. Greedily keep intervals that don't overlap with the last kept one. Count the removed intervals. This is the classic activity selection problem in disguise.
Key idea: Sort by end time. Track lastEnd. For each interval: if start >= lastEnd, keep it (update lastEnd). Otherwise, remove it (increment count). The number of removals = total - max non-overlapping. Greedy: earliest finish leaves most room.
LC 134. Gas Station
Why this pattern:
Circular surplus greedy (Template 4). Track running surplus (gas - cost). When it goes negative, reset start to the next station. If total surplus >= 0, the last start is the answer. This teaches the 'reset on failure' greedy strategy.
Key idea: If totalSurplus < 0, no solution exists. Otherwise, scan with currentSurplus. When it drops below 0, no station from start to i works — set start = i+1 and reset. The remaining stations must have enough surplus to cover the earlier deficit.
LC 45. Jump Game II
Why this pattern:
Greedy BFS / expansion (Template 2). Think of it as BFS by levels: each 'jump' is a level. At each level, find the farthest reachable position. When you exhaust the current level, take a jump. This is the hardest greedy expansion problem.
Key idea: Track currentEnd (end of current level) and farthest (max reachable from current level). When i reaches currentEnd, increment jumps and set currentEnd = farthest. This is equivalent to BFS where each level is a jump, and greedy picks the farthest next level.
Practice strategy
For each problem: (1) Identify the greedy criterion before coding — what does "locally best" mean here? (2) Try to find a counterexample that breaks the greedy choice. If you can't, proceed. (3) After solving, write one sentence: "Greedy works because [exchange argument or invariant]."
Common Mistakes
These are the traps that catch people on greedy problems. Learn them here so you don't learn them in an interview.
Applying greedy when it doesn't work
You assume greedy is correct without verifying. Classic trap: coin change with denominations {1, 3, 4} and target 6. Greedy picks 4+1+1=3 coins, but optimal is 3+3=2 coins. Greedy fails because the coin system lacks the 'greedy choice property.'
✅Always try to find a counterexample before committing to greedy. If the problem involves choosing items with weights/values (knapsack-style), greedy almost never works — use DP. If you can't find a counterexample after 2-3 tries, greedy is likely correct.
Sorting by the wrong criterion
You sort intervals by start time when you should sort by end time (or vice versa). For activity selection, sorting by start time gives the wrong answer: [1,100] would be picked first, blocking everything else.
✅Ask: 'What does locally optimal mean here?' For maximum non-overlapping: sort by end time (earliest finish). For merging intervals: sort by start time. For minimum platforms: sort events by time. The sort criterion IS the greedy strategy — get it right.
Not recognizing when greedy needs a heap
Some greedy problems need the 'current best' element repeatedly, which requires a priority queue. You try to solve Meeting Rooms II with just sorting, but you need a min-heap to track the earliest-ending room.
✅If the greedy choice is 'pick the best available resource,' you likely need a heap. Sort the input by one criterion (e.g., start time), then use a heap to track the other criterion (e.g., earliest end time of active rooms).
Off-by-one in jump/coverage problems
In Jump Game II, you loop through all indices including the last one, causing an extra jump. Or you check i === currentEnd at the wrong time, missing the final jump.
✅Loop until nums.length - 1 (not nums.length). You don't need to 'jump from' the last index — you just need to reach it. Also, update farthest BEFORE checking if i === currentEnd.
Not proving correctness (exchange argument)
You code a greedy solution that passes all test cases but can't explain WHY it's correct. In an interview, the interviewer asks 'How do you know greedy works here?' and you're stuck.
✅Practice the exchange argument: 'Suppose the optimal solution makes a different choice at step k. I can swap in the greedy choice without making things worse because [reason].' This is the standard proof technique for greedy algorithms.
Confusing greedy with DP
The problem has optimal substructure, so you think greedy works. But it also has overlapping subproblems where choices interact — greedy gives a suboptimal answer. Example: 0/1 Knapsack looks greedy-friendly but isn't.
✅Greedy = one irrevocable choice per step, no backtracking. DP = try all choices, cache results. If taking item A affects the value of item B, choices interact → use DP. If items are independent, greedy may work.
Interview Strategy
Greedy problems are tricky in interviews because the code is often simple but the justification is hard. Here's how to present greedy solutions to maximize your score.
The 5-Step Interview Flow
Clarify & Identify the Optimization Target
'I need to maximize the number of non-overlapping activities? Can activities share endpoints? Is the input sorted?' — Clarify what 'optimal' means. Greedy needs a clear objective.
State the Brute Force
'The brute force tries all 2ⁿ subsets — exponential. But I think a greedy approach works here.' — Briefly mention the brute force to show you understand the problem space.
Propose the Greedy Strategy & Justify It
'I'll sort by end time and always pick the earliest-finishing activity. This works because finishing early never blocks future options — any activity compatible with a later finish is also compatible with an earlier one.' — State the criterion AND the exchange argument.
Verify with an Example
Trace through a small example showing the greedy choices. 'For [[1,4],[3,5],[5,7],[8,11]]: pick [1,4], skip [3,5] (overlaps), pick [5,7], pick [8,11]. Result: 3.' — This builds confidence that the strategy works.
Code & Analyze
Write clean code with the sort + scan structure. State complexity: 'O(n log n) for sorting, O(n) for the scan. Total: O(n log n) time, O(1) extra space.' — Greedy solutions are usually short; make the code match.
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "How do you know greedy is correct?" | Use the exchange argument: 'In any optimal solution, I can swap in the greedy choice without making things worse. Here's why: [specific reason for this problem].' Practice this for each problem type. |
| "What if greedy doesn't work?" | Fall back to DP. 'If choices interact (taking one item affects the value of others), I'd use DP to explore all options. Greedy works here because choices are independent.' Show you know the boundary. |
| "Can you prove it more formally?" | Proof by contradiction: 'Assume the optimal solution differs from greedy at step k. I can swap the optimal choice for the greedy choice. The result is at least as good because [reason]. Contradiction — greedy IS optimal.' |
| "Why sort by end time, not start time?" | For activity selection: 'Sorting by start time might pick [1,100] first, blocking everything. Sorting by end time picks the activity that finishes earliest, leaving maximum room. The exchange argument only works for end-time sorting.' |
| "What if we need the actual subset, not just the count?" | Store the selected items during the greedy scan. The greedy order gives you the optimal subset directly — no backtracking needed. |
| "Can you solve it without sorting?" | Some greedy problems don't need sorting (Gas Station, Jump Game, Stock Trading). But interval problems almost always need sorting as the first step. If the input is already sorted, mention the O(n) improvement. |
The meta-skill
Interviewers love greedy problems because they test judgment, not just coding. The code is often 10-15 lines. The hard part is: (1) recognizing that greedy works, (2) choosing the right criterion, and (3) justifying correctness. Practice the exchange argument for each problem type — it's the single most important skill for greedy interviews.
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
Core idea: Make the locally optimal choice at each step; never reconsider. Works when local optimality leads to global optimality
Two properties: Greedy choice property (local best is part of global best) + optimal substructure (remaining problem is smaller same problem)
Exchange argument: The proof technique: show that swapping any optimal choice for the greedy choice never makes things worse
Sort + scan: Most common template. Sort by the greedy criterion (end time, size, ratio), then scan and pick greedily
Interval scheduling: Sort by END time. Pick earliest finish. This maximizes non-overlapping count. Exchange: earlier finish ≥ later finish
Jump / coverage: Track farthest reachable. When current range exhausted, take a jump. Think of it as BFS by levels
Stock trading: Collect every upward movement: if prices[i] > prices[i-1], add the difference. Greedy: never miss a profit
Gas station: Track running surplus. When negative, reset start. If total surplus ≥ 0, last start works
Partition labels: Track last occurrence of each char. Extend partition to max last-occurrence. Cut when index reaches boundary
Greedy vs DP: Greedy: choices are independent, one pass. DP: choices interact, need to try all options. When in doubt, try greedy first
Counterexample test: Before committing, try 2-3 small inputs to break the greedy choice. If you can't break it, proceed
Mental trigger: "Maximize/minimize by sequential choices" + "sorting reveals order" + "local best never hurts" → Greedy
Decision Flowchart
Does the problem ask to maximize/minimize something by making sequential choices? ├── NO → Greedy probably doesn't apply. Consider other patterns. └── YES → Do choices interact? (Does picking item A affect the value of item B?) ├── YES → Use Dynamic Programming (choices are dependent) │ Examples: 0/1 Knapsack, Coin Change (non-standard), Edit Distance └── NO → Can you find a counterexample that breaks the greedy choice? ├── YES → Greedy fails. Use DP or Backtracking. └── NO → Greedy likely works! What's the greedy criterion? ├── Intervals? → Sort by end time (scheduling) or start time (merging) ├── Jumps/coverage? → Track farthest reachable (expansion) ├── Assignment? → Sort both sides, match smallest to smallest ├── Accumulation? → Track running surplus/profit └── Digits/strings? → Use a stack, drop suboptimal elements
Greedy vs. DP — Quick Reference
| Problem | Greedy? | Why / Why Not |
|---|---|---|
| Activity Selection | ✅ Yes | Earliest finish never blocks future options (exchange argument) |
| Fractional Knapsack | ✅ Yes | Take highest value/weight ratio first (items are divisible) |
| 0/1 Knapsack | ❌ No | Taking a heavy item might block a better combination (choices interact) |
| Coin Change (US coins) | ✅ Yes | Standard denominations have the greedy property |
| Coin Change (arbitrary) | ❌ No | Non-standard denominations break greedy (e.g., {1,3,4} target 6) |
| Jump Game | ✅ Yes | Farthest reachable only grows; reaching further never hurts |
| Longest Increasing Subsequence | ❌ No | Picking the locally smallest next element can miss longer sequences |
| Huffman Coding | ✅ Yes | Combining two smallest frequencies first is provably optimal |
One last thing
Greedy is the most elegant pattern in DSA — the solutions are short, fast, and satisfying. But it's also the most dangerous because it's easy to apply incorrectly. The difference between a correct greedy solution and a wrong one is the exchange argument. Every time you use greedy, ask yourself: "If I swap the greedy choice into any optimal solution, does it stay optimal?" If yes, you're golden. If you're not sure, reach for DP.