LearnAbout

Basic Greedy

On this page
Quick reference
Activity selectionO(n log n)
Two-pointer greedy matchingO(n log n)
Reachability scanO(n)
Exhaustively trying every choice (the alternative to greedy)O(2^n) typical
The Idea

A greedy algorithm makes the best-looking choice available right now, locks it in, and never looks back to reconsider it. No backtracking, no "what if I'd chosen differently three steps ago" โ€” just: given what's in front of you this instant, take the best option and move on.

Think about making change with the fewest coins, using US denominations โ€” pennies, nickels, dimes, quarters. The greedy way: always grab the biggest coin that still fits under the remaining amount. Owe 41 cents? Take a quarter (16 left), then a dime (6 left), then a nickel (1 left), then a penny. Four coins, and it happens to be the fewest possible. For US coins, always taking the biggest coin that fits happens to always produce the optimal answer โ€” but that's a fact about these specific denominations, not a law of the universe. The real skill in greedy algorithms was never "grab the biggest thing first" โ€” it's knowing when that instinct is actually safe to trust for the problem in front of you, and when it isn't. Break It below shows exactly where this coin analogy stops working.

Greedy is fast precisely because it never looks back โ€” and "never looks back" is also exactly how it can walk past the actual best answer without noticing. The choice itself isn't the hard part; proving the choice is safe for this specific problem is.
Build It
The shape of a greedy algorithm
# greedy isn't a data structure โ€” it's a strategy applied to plain arrays,
# usually after a sort that makes "locally best" well-defined:
#
#   sort the input by whatever makes the next choice comparable
#   walk through once, left to right
#   at each step, take the option that looks best right now
#   never revisit or undo a choice already made
#
# three classic shapes this takes, below: picking non-overlapping intervals,
# matching two sorted lists against each other, and tracking one running value
# while scanning once.
interval / activity selection โ€” sort by end time, take what still fits
function activitySelection(activities):     // each activity is (start, end)
    sort activities by end ascending
    chosen = []
    lastEnd = -infinity
    for (start, end) in activities:
        if start >= lastEnd:                 // doesn't overlap the last one taken
            chosen.append((start, end))
            lastEnd = end
    return chosen
Why sort by end time and not start time: whichever activity finishes soonest leaves the most room free for everything that comes after. Locking that one in first can never cost you room you'd otherwise have had โ€” that's the safety argument, not just an intuition.
two-pointer greedy matching โ€” smallest available resource to smallest need that still fits
function matchSmallestFit(needs, resources):
    sort needs ascending
    sort resources ascending
    i = 0                            // pointer into needs
    j = 0                            // pointer into resources
    matched = 0
    while i < needs.length and j < resources.length:
        if resources[j] >= needs[i]:  // this resource is big enough to cover this need
            matched += 1
            i += 1                    // this need is satisfied โ€” move to the next need
        j += 1                        // this resource is spent either way โ€” move to the next one
    return matched
Giving the smallest need the smallest resource that still covers it is safe because it keeps every bigger resource in reserve for bigger needs โ€” using a bigger resource on a small need can only ever hurt some larger need later, never help.
reachability tracking โ€” furthest index reachable so far, one scan
function canReachEnd(jumps):
    furthest = 0
    for i from 0 to jumps.length - 1:
        if i > furthest:              // can't even get to i, let alone past it
            return false
        furthest = max(furthest, i + jumps[i])
    return furthest >= jumps.length - 1
Know It

Every example above is dominated by its sort, if it needs one: O(n log n) time, with the linear scan after it disappearing into that bound. The reachability scan needs no sort at all, so it's O(n). Extra space is O(1) to O(n) โ€” usually just a few running variables, occasionally an output list. The entire appeal is what greedy avoids: trying every possible combination of choices is often exponential, and greedy replaces that with a single pass โ€” but only for problems where the greedy choice is actually provably safe, which is a property of the problem, not something you get for free by choosing greedily.

OperationTimeSpaceWhy
Activity selectionO(n log n)O(1) extraSorting by end time dominates; the scan afterward touches each activity once.
Two-pointer greedy matchingO(n log n)O(1) extraSorting both sides dominates; the two pointers then each walk their list once, together.
Reachability scanO(n)O(1)No sort needed โ€” the state is a single running maximum updated once per index.
Exhaustively trying every choice (the alternative to greedy)O(2^n) typicalO(n) (recursion)Without a proof that the greedy choice is safe, the honest fallback is trying every combination โ€” which is exactly the cost greedy is trying to avoid, and exactly why greedy is only worth reaching for once you can justify it.
Break It

The classic counterexample โ€” greedy is not always optimal

coin denominations {1, 3, 4}, making change for 6

Greedy grabs the biggest coin that fits, every time: 4, then 1, then 1 โ€” three coins. But 3 + 3 is two coins, strictly better, and greedy never finds it because after taking the 4 it never reconsiders. This is the whole lesson from The Idea made concrete: the coin analogy works for US currency and breaks here, on denominations that are just as reasonable-looking. Greedy needs a proof it's safe for the specific problem, not a vibe that it probably is.

Forgetting to sort first

the greedy choice depends on comparing items by some order, but the code walks them in input order instead

Activity selection's safety argument depends entirely on visiting the soonest-ending activity first โ€” feed it activities in random input order and the same "take it if it doesn't overlap" logic produces a smaller, wrong answer, because it commits to a bad early choice before ever seeing the good one.

Committing to the greedy choice without checking why it's safe

picking "looks best right now" without a reason it can't backfire later

Every working example above has an actual argument for why the locally-best choice can't cost you the globally-best answer โ€” Build It states each one. Skipping that check and just trusting the greedy instinct is how you end up with a fast, confident, wrong answer: it'll pass on the first three test cases you try it on and fail on the fourth.

Off-by-one on interval boundaries

comparing end times with <= vs. <

If one activity ends exactly when the next one starts, are they overlapping or not? start >= lastEnd treats a shared boundary point as fine (back-to-back, non-overlapping); start > lastEnd would reject it. Both are defensible โ€” but the problem statement picks one, and using the wrong comparison silently drops or admits one extra interval at every boundary case without ever throwing an error.

Use It
Assign Cookies
The two-pointer matching pattern from Build It, verbatim: sort both children's greed factors and cookie sizes, give the smallest cookie that satisfies each child.
Easy
Non-overlapping Intervals
activitySelection() from Build It answers 'how many can I keep'; this problem asks 'how many do I have to remove' โ€” that's just total count minus what activitySelection() keeps.
Medium
Jump Game
canReachEnd() from Build It, verbatim.
Medium
Jump Game II
Same furthest-reachable tracking as Jump Game, extended to also count how many times you're forced to commit to a new jump.
Medium
Gas Station
One pass tracking a running tank total; the index right after wherever that running total drops lowest is the only possible valid start.
Medium
Lemonade Change
At each sale, greedily give change using the biggest bills first โ€” same 'always take the biggest that fits' instinct as the coin-change analogy from The Idea, and this time it's safe.
Easy
Best Time to Buy and Sell Stock II
Greedily take every single day-over-day gain you see โ€” no need to plan actual buy/sell points in advance.
Medium
Minimum Number of Arrows to Burst Balloons
activitySelection()'s sort-by-end shape again, but counting how many non-overlapping groups exist rather than listing the intervals themselves.
Medium
Boats to Save People
matchSmallestFit() from Build It, with a twist: two pointers from opposite ends of the sorted array, pairing the lightest person with the heaviest one they can still share a boat with.
Medium
Partition Labels
Track the furthest last-occurrence index of any character seen so far in the current partition โ€” the same 'furthest reachable so far' shape as canReachEnd(), closing a partition the moment the scan catches up to it.
Medium