Skip to content

Algorithmic Patterns

Greedy algorithms

Always pick the locally optimal choice

A greedy algorithm builds an answer one decision at a time, and at each step it grabs whatever looks best right now - the largest coin, the earliest-finishing meeting, the cheapest edge - without ever reconsidering. It's the most optimistic strategy in computer science: it assumes a trail of locally best moves lands on the globally best answer. Sometimes that faith is rewarded with a short, beautiful, provably optimal algorithm. Just as often it walks confidently off a cliff. The entire skill is knowing which is which - and that hinges on two precise properties. We'll build the intuition on interval scheduling, where the right greedy rule is optimal and a very plausible wrong one quietly loses.

The one-line idea

Dynamic programming tries every option for each subproblem and keeps the best. Greedy is DP's reckless sibling: it looks at the choices, picks one by a fixed rule, and commits - no table, no backtracking, no second-guessing. That's why it's fast. It's also why it's only correct when one local choice can never doom you later.

Two properties that make greedy safe

Greedy is not a heuristic you cross your fingers on - for a specific class of problems it is provably optimal. A problem is safely greedy when it has both of these at once:

1. The greedy-choice property. There exists a locally optimal choice - decided from what's in front of you, without solving any subproblem first - that is part of some globally optimal solution. In plain terms: making the greedy pick now never forecloses your ability to reach a best answer. (Contrast this with DP, which must first solve subproblems before it can compare options.)

2. Optimal substructure. Once you've locked in that first choice, what remains is a smaller instance of the same problem, and an optimal solution to the whole is the greedy choice plus an optimal solution to that remainder. This is the same property DP relies on - it's what lets you recurse. Greedy just adds the stronger promise that you don't have to explore the remainder's options; the rule picks correctly again.

Optimal substructure alone is not enough

The 0/1 knapsack problem has optimal substructure but fails the greedy-choice property: grabbing the item with the best value-per-weight first can lock you out of the optimal set, so knapsack needs DP. Greedy demands the extra guarantee that the locally best choice is always safe. When it's missing, greedy gives a fast, plausible, wrong answer.

Activity selection: pick the earliest finish

Here is the canonical greedy problem. You're given a set of activities, each with a start and finish time, and a single resource (one room, one CPU). Two activities conflict if they overlap in time. Goal: schedule the maximum number of non-conflicting activities. The winning greedy rule is almost suspiciously simple: repeatedly take the compatible activity that finishes earliest. Finishing early frees the resource soonest, leaving the most room for everything after.

The playground below lets you feel why the rule matters. Drag any bar left or right to change when that activity happens, then pick a greedy rule and step through the scan. Watch the same input give a maximum set under "earliest finish" - and a smaller, worse set under two rules that sound just as reasonable.

Interval-scheduling playground drag bars - switch the rule - step the scan
consideringkept (compatible)skipped (overlaps)last kept finish
Greedy ruleoptimal 4 - this rule 4

Sorted by earliest finish. Scan the queue left to right; keep an activity only if it starts at or after the last kept one finishes.

BCDAEF
Queue in earliest finish order. Press Next to consider the first activity.

Time runs left to right. The greedy scan walks the queue in sorted order, keeping an activity only if it starts at or after the dashed line (the last kept activity's finish). Only earliest finish is guaranteed to reach a maximum set.

The aha: greedy is only as good as its rule

All three rules are greedy - each commits to a locally best pick and never backtracks. But on this input earliest start grabs the long bar that hogs the whole morning, and shortest first takes a stubby interval that straddles two good ones; both end with fewer meetings. Only earliest finish is provably optimal. Greedy's speed is free; its correctness is not - it must be earned with the right rule and a proof that the rule is safe.
def activity_selection(activities):         # activities: list of (start, finish)
    activities.sort(key=lambda a: a[1])   # 1. sort by EARLIEST FINISH - the greedy key
    chosen, last_finish = [], float("-inf")
    for start, finish in activities:
        if start >= last_finish:            # compatible with everything kept so far?
            chosen.append((start, finish))
            last_finish = finish            # commit - this pick is never undone
    return chosen                          # provably a maximum-size compatible set
Activity selection - the whole algorithm

One sort plus one linear scan: total, dominated by the sort. No table, no recursion, no memory beyond the running last_finish. That economy is the whole appeal of greedy - when you can prove it works.

Why earliest-finish is optimal: the exchange argument

How do you prove a greedy rule is safe rather than just observing it on examples? The standard tool is the exchange argument: show that any optimal solution can be transformed, choice by choice, into the greedy one without ever getting worse. If the greedy solution is no worse than an arbitrary optimum, it is itself optimal.

The exchange, in one move

Let be the activity with the earliest finish time overall, and let be any optimal schedule. Sort by finish time and call its first activity . By definition finishes no later than : . So swap out and in. The new set is still conflict-free - nothing else in started before finished, hence not before finishes either - and , so is also optimal and it contains the greedy choice. Now delete everything overlapping and recurse on the remainder (that's the optimal substructure). Each step keeps the greedy pick without shrinking the answer, so greedy matches an optimum.

This "an optimal solution can be nudged toward the greedy one" template proves most greedy algorithms. It's also why Huffman coding - repeatedly merging the two least-frequent symbols to build an optimal prefix code - is correct: the two rarest symbols can always be pushed to the deepest, longest codewords of some optimal tree, so merging them first is safe. Same exchange idea, different problem.

Greedy vs. DP: commit, or explore?

The cleanest way to hold these two apart: both need optimal substructure, but they differ on how much they trust a local choice. DP assumes nothing - at each subproblem it evaluates every option and keeps the best, paying with a table and -style time. Greedy assumes the local rule is always right - it commits to one option and pays almost nothing. Greedy is a strict special case: when the greedy-choice property holds, the DP's "try everything" collapses to "try the one obvious thing," and an or algorithm becomes . When it doesn't hold, that shortcut silently returns a wrong answer - which is exactly what you watched two of the three rules do above.

Check yourself

Activity selection is solved optimally by repeatedly choosing the compatible activity that:

A greedy algorithm is guaranteed to be optimal only when the problem has:

What is the greedy-choice property, and how does the exchange argument prove earliest-finish activity selection optimal?

Recall the two-property test and the proof template. Try to state it, then check.

Lock it in

  • Greedy builds an answer one locally best choice at a time and never reconsiders - fast, but only sometimes correct.
  • A greedy rule is provably optimal only when the problem has both the greedy-choice property and optimal substructure; optimal substructure alone is not enough (0/1 knapsack needs DP).
  • Activity selection: repeatedly take the compatible activity that finishes earliest. One sort plus one scan, dominated by the sort.
  • The exchange argument proves safety: any optimal solution can be nudged, choice by choice, into the greedy one without ever getting worse.

Primary source

For a rigorous lecture treatment - activity selection, the exchange argument, Huffman coding, and where greedy breaks - work through Abdul Bari's Greedy Algorithms playlist. For pattern-by-pattern practice on interval and greedy problems, use the Greedy and Intervals sections of the NeetCode Roadmap, where activity selection sits beside its cousins (non-overlapping intervals, merge intervals, jump game).

Ask your teacher

This exact idea reappears the moment a language model generates text. Greedy decoding picks the single highest-probability token at each step - the locally best choice - commits, and moves on. Fast, and often fine. But like earliest start on the wrong input, a locally best token can lock you out of the globally best sentence: the highest-probability first word may lead into a low-probability dead end. That gap is precisely why beam search exists - it keeps several candidate sequences alive instead of committing to one, buying back some of what greedy throws away. Generating one token at a time is itself a recurrence, the thread picked up in recursion and recurrence to autoregression in the LLM course. Ask me how beam search trades compute for a better sequence, why greedy decoding still dominates in practice, or how this mirrors the greedy-vs-DP tension you just saw.