Mid-level 6 min · March 06, 2026

Greedy Algorithm — Why Earliest Start Time Causes Timeouts

Payment jobs timed out hourly: greedy by earliest start time blocked shorter jobs.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • 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.
Plain-English First

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.

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.

When to Use Greedy
Use greedy when the problem has proven optimal substructure and the greedy choice property. Always test with a counterexample generator before trusting it in production. When in doubt, implement a DP fallback for correctness comparison.
Production Insight
In production systems, greedy algorithms are often used as fast heuristics even when optimality isn't guaranteed. The key is to monitor the quality metric (e.g., number of jobs completed, profit) and flag deviations. A/B testing the greedy result against a slower optimal solver on sampled data can catch regressions early.
Key Takeaway
Greedy is fast and simple but not universally optimal. Always validate both the greedy choice property and optimal substructure before deployment.

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).

IntervalScheduler.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package io.thecodeforge.algorithms.greedy;

import java.util.*;

/**
 * Production-grade implementation of the Activity Selection Problem.
 * Time Complexity: O(N log N) due to sorting.
 */
public class IntervalScheduler {
    public static class Interval {
        int start, end;
        public Interval(int start, int end) { this.start = start; this.end = end; }
        @Override
        public String toString() { return "(" + start + ", " + end + ")"; }
    }

    public List<Interval> selectMaxActivities(List<Interval> intervals) {
        if (intervals == null || intervals.isEmpty()) return Collections.emptyList();

        // Greedy Choice: Sort by finish time
        intervals.sort(Comparator.comparingInt(a -> a.end));

        List<Interval> result = new ArrayList<>();
        Interval lastSelected = intervals.get(0);
        result.add(lastSelected);

        for (int i = 1; i < intervals.size(); i++) {
            Interval current = intervals.get(i);
            // If this activity starts after or when the last one ends, pick it
            if (current.start >= lastSelected.end) {
                result.add(current);
                lastSelected = current;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        IntervalScheduler scheduler = new IntervalScheduler();
        List<Interval> input = Arrays.asList(
            new Interval(1, 4), new Interval(3, 5), new Interval(0, 6), 
            new Interval(5, 7), new Interval(3, 8), new Interval(5, 9),
            new Interval(6, 10), new Interval(8, 11), new Interval(12, 16)
        );
        System.out.println("Selected intervals: " + scheduler.selectMaxActivities(new ArrayList<>(input)));
    }
}
Output
Selected intervals: [(1, 4), (5, 7), (8, 11), (12, 16)]
Why Earliest Finish Time Wins
  • 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.
Production Insight
Sorting by start time instead of end time is the #1 bug in production interval schedulers.
Earliest start time almost always selects fewer activities because it picks long early blockers.
Rule: if you're scheduling meetings or jobs, always sort by finish time — test with a simple counterexample first.
Key Takeaway
Earliest finish time is the only correct greedy for unweighted interval scheduling.
Start time and shortest duration are traps.
Always exchange-argue before trusting a greedy choice.
Choosing the Right Sorting Strategy for Interval Scheduling
IfGoal: maximize number of non-overlapping intervals
UseSort by end time (ascending). Prove via exchange argument.
IfGoal: maximize total length of scheduled time
UseSort by start time? Not greedy — use DP or interval graph
IfIntervals have weights (profit)
UseWeighted interval scheduling — DP, not greedy
IfIntervals can be preempted (split)
UseEarliest deadline first — sort by end time

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.

interval_scheduler.cppCPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct Interval {
    int start, end;
    Interval(int s, int e) : start(s), end(e) {}
};

vector<Interval> selectMaxActivities(vector<Interval>& intervals) {
    if (intervals.empty()) return {};

    // Greedy: Sort by finish time
    sort(intervals.begin(), intervals.end(),
         [](const Interval& a, const Interval& b) { return a.end < b.end; });

    vector<Interval> result;
    result.push_back(intervals[0]);
    int lastEnd = intervals[0].end;

    for (size_t i = 1; i < intervals.size(); ++i) {
        if (intervals[i].start >= lastEnd) {
            result.push_back(intervals[i]);
            lastEnd = intervals[i].end;
        }
    }
    return result;
}

int main() {
    vector<Interval> intervals = {
        {1, 4}, {3, 5}, {0, 6}, {5, 7},
        {3, 8}, {5, 9}, {6, 10}, {8, 11}, {12, 16}
    };
    auto selected = selectMaxActivities(intervals);
    for (auto& i : selected)
        cout << "(" << i.start << ", " << i.end << ") ";
    return 0;
}
Output
(1, 4) (5, 7) (8, 11) (12, 16)
C++ Performance Note
The C++ sort uses introsort (O(N log N)) and the loop is O(N). The vector holds the selected intervals, which is optimal memory usage. For massive data, consider using arrays and reserving capacity upfront.
Production Insight
In C++ production schedulers, pay attention to the comparator — a wrong sort order can silently degrade throughput. Always validate with a brute-force check for small test cases before deploying.
Key Takeaway
The greedy algorithm for interval scheduling is language-agnostic. Sort by finish time, iterate, and pick non-overlapping intervals.

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.

greedy_check.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- io.thecodeforge.analytics
-- Tracking greedy vs optimal counts in a simulated environment
CREATE TABLE denominations (coin_val INT);
INSERT INTO denominations VALUES (1), (3), (4);

-- A simple query to show how many of the LARGEST coin greedy would take
-- for an amount of 6.
SELECT 
    6 AS target_amount,
    MAX(coin_val) AS greedy_first_pick,
    (6 / MAX(coin_val)) AS coins_taken,
    (6 % MAX(coin_val)) AS remainder_to_process
FROM denominations
WHERE coin_val <= 6;
-- This logic clearly shows greedy leaves a remainder of 2, requiring 2 more coins.
Output
target_amount: 6 | greedy_first_pick: 4 | coins_taken: 1 | remainder_to_process: 2
Coin Systems: Canonical vs Non-Canonical
A coin system is canonical if the greedy algorithm produces optimal change for all amounts. The US dollar system (1,5,10,25) is canonical. Many newer currencies (euro: 1,2,5,10,20,50) are also canonical. But arbitrary sets like (1,3,4) or (1,5,10,12,20,25) may not be. Always test with a brute-force checker before relying on greedy in production.
Production Insight
A change in coin supply (e.g., a new $12 bill) can silently break greedy optimality.
Run regression tests with known problematic amounts after any coin system update.
Rule: if the coin system changes, re-run the canonical test suite — don't assume greedy still works.
Key Takeaway
Greedy coin change works only for canonical systems.
Test with known counterexamples before deploying.
When in doubt, DP never lies.
Coin Change Algorithm Decision
IfCoin system is canonical (US, Euro)
UseGreedy works. O(N log N) sorting + O(amount) iteration.
IfCoin system may not be canonical (custom denominations)
UseUse dynamic programming — exact O(N * amount) is safe.
IfAmount is extremely large (billions)
UseGreedy may be needed for speed, but validate canonical 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).

Greedy Fails on Longest Path
The longest path problem is NP-hard in general (even for DAGs it requires DP). Greedy picks the locally largest edge, but the optimal path might require a smaller initial edge that leads to a much richer subsequent route. Always use DP or topological sorting for longest path in DAGs.
Production Insight
In route optimization or resource allocation systems, never rely on greedy for longest-path variants. The cost of a wrong early decision compounds downstream. Use DP with topological ordering (O(V+E)) for DAGs, or apply A* with admissible heuristics for general graphs.
Key Takeaway
Greedy fails on longest path because it sacrifices long-term gain for short-term reward. Use DP or topological ordering for DAGs.

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.

HuffmanEncoder.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package io.thecodeforge.algorithms.greedy;

import java.util.*;

public class HuffmanEncoder {
    static class Node implements Comparable<Node> {
        char ch; int freq; Node left, right;
        Node(char ch, int freq, Node left, Node right) {
            this.ch = ch; this.freq = freq; this.left = left; this.right = right;
        }
        public int compareTo(Node other) { return this.freq - other.freq; }
    }

    public static Map<Character, String> encode(Map<Character, Integer> frequencies) {
        PriorityQueue<Node> pq = new PriorityQueue<>();
        for (var entry : frequencies.entrySet())
            pq.add(new Node(entry.getKey(), entry.getValue(), null, null));

        while (pq.size() > 1) {
            Node left = pq.poll();
            Node right = pq.poll();
            pq.add(new Node('\0', left.freq + right.freq, left, right));
        }

        Map<Character, String> codes = new HashMap<>();
        buildCodes(pq.peek(), "", codes);
        return codes;
    }

    private static void buildCodes(Node node, String code, Map<Character, String> codes) {
        if (node == null) return;
        if (node.left == null && node.right == null) {
            codes.put(node.ch, code);
        } else {\n            buildCodes(node.left, code + \"0\", codes);\n            buildCodes(node.right, code + \"1\", codes);\n        }\n    }\n\n    public static void main(String[] args) {\n        Map<Character, Integer> freq = Map.of('a',5,'b',9,'c',12,'d',13,'e',16,'f',45);\n        System.out.println(encode(freq));\n    }\n}",
        "output": "{a=1100, b=1101, c=100, d=101, e=111, f=0}"
      }

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:

  1. 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.
  2. 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.

ExchangeArgumentDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package io.thecodeforge.algorithms.greedy;

/**
 * Demonstration of the exchange argument for interval scheduling.
 * Assume optimal solution O and greedy solution G.
 * Let i be the first activity where they differ.
 * O has activity j (start_j, end_j) where end_j >= end_i (greedy's choice).
 * Swap j with i in O — O' is still optimal because i ends no later than j
 * and doesn't overlap with earlier activities.
 */
public class ExchangeArgumentDemo {
    // No executable code — this is a conceptual proof.
    public static void main(String[] args) {
        System.out.println("Proof: By induction on steps. Greedy stays ahead.");
    }
}
Output
Proof: By induction on steps. Greedy stays ahead.
The Two Pillars of Greedy Correctness
Every correct greedy algorithm must satisfy two properties: (1) Optimal substructure — the problem can be broken into smaller subproblems, and the optimal solution combines optimal solutions of subproblems. (2) Greedy choice property — a globally optimal solution can be arrived at by making a locally optimal (greedy) choice.
Production Insight
Skipping correctness proofs is the root cause of countless production bugs in scheduling, networking, and optimisation systems.
Always write a counterexample generator and run it on random small instances before deploying a greedy solution.
Rule: if you can't prove it, don't trust it — brute-force verify for small N first.
Key Takeaway
Exchange argument or greedy stays ahead — pick one and prove.
Optimal substructure AND greedy choice property are both required.
Without proof, a greedy algorithm is just a heuristic with a bug waiting to happen.

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.

DockerfileDOCKER
1
2
3
4
5
6
7
# io.thecodeforge.infrastructure
FROM eclipse-temurin:17-jdk-jammy
WORKDIR /app
COPY . /app
RUN javac io/thecodeforge/algorithms/greedy/IntervalScheduler.java
# Run with memory limits to test efficiency
CMD ["java", "-Xmx128m", "io.thecodeforge.algorithms.greedy.IntervalScheduler"]
Output
Successfully built greedy-benchmark-container
Benchmarking Greedy vs DP
When comparing greedy and DP solutions, ensure you test with identical input sizes and hardware constraints. Docker with fixed memory limits gives reproducible results. Use JMH (Java Microbenchmark Harness) for accurate microbenchmarks — System.nanoTime() is not enough.
Production Insight
Greedy algorithms are memory-efficient but can be CPU-bound if the data structure (e.g., priority queue) isn't chosen carefully.
For intervals, a simple sort + scan uses O(1) extra memory — monitor GC pressure.
Rule: profile greedy solutions with realistic data volumes before declaring victory.
Key Takeaway
Containerised benchmarking with memory limits.
Choose data structures wisely — priority queue vs sorting.
Profile before shipping.

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.

  1. Fractional Knapsack (GeeksforGeeks / LeetCode) — The classic greedy success story. Sort by value-to-weight ratio and take items fully or fractionally.
  2. Jump Game (LeetCode 55) — Determine if you can reach the last index. The greedy solution tracks the furthest reachable index.
  3. Jump Game II (LeetCode 45) — Minimum jumps to reach the end. Greedy BFS approach.
  4. Job Sequencing with Deadlines (GeeksforGeeks) — Maximise profit when each job has a deadline and profit. Sort by profit and slot jobs greedily.
  5. Minimum Number of Arrows to Burst Balloons (LeetCode 452) — Interval scheduling variant. Sort by end coordinate.
  6. Non-overlapping Intervals (LeetCode 435) — Find maximum number of non-overlapping intervals (or minimum removals). Same greedy as interval scheduling.
  7. Assign Cookies (LeetCode 455) — Simple greedy: sort children's greed and cookie sizes, assign smallest cookie that satisfies.
  8. 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.

How to Practice Effectively
For each problem, first write a brute-force solution (backtracking or DP) for small N to verify correctness. Then implement the greedy solution and compare. This validates the greedy choice property on the fly.
Production Insight
Many production greedy bugs come from misapplying a pattern that worked on a different problem. After practicing, create a mental checklist: 'Does my problem have the same structure as interval scheduling, coin change, or Huffman? Does the greedy choice property hold?'
Key Takeaway
Solve at least 8 different greedy problems to build pattern recognition. Always verify greedy against brute-force for small inputs until you can confidently prove correctness.

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.

CriterionGreedyDynamic Programming
ApproachMakes a local optimal choice at each step, commits and never backtracks.Solves subproblems recursively, stores results to avoid recomputation.
Correctness conditionsRequires both greedy choice property and optimal substructure.Requires only optimal substructure (no greedy property needed).
Time complexityUsually O(N log N) or O(N)Typically O(N²) or O(N * W)
Space complexityO(1) extra or O(N) for sortingOften O(N * W) for tabulation
BacktrackingNoneYes — memoization or tabulation revisits subproblems.
RobustnessFragile — a small change in problem constraints can break greedy.Robust — handles a wide range of constraint variations.
Example problemsInterval scheduling, Huffman, Dijkstra, Fractional Knapsack0/1 Knapsack, Edit Distance, LCS, Weighted Interval Scheduling
Production riskHigh if correctness proof is skippedLow 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).

Hybrid Approach in Production
Some systems use greedy as a heuristic for large inputs and DP for small inputs where optimality is critical. This hybrid balances speed and correctness. For example, in ad allocation, greedy may handle daily budget constraints while DP optimises the last few slots.
Production Insight
When deploying a greedy solution, always implement a DP fallback in a shadow mode to compare results on a fraction of traffic. If the greedy result deviates too much from DP, alert the team. This catches silent regressions early.
Key Takeaway
Greedy is faster but fragile; DP is slower but robust. Know when to use each, and consider a hybrid strategy for production.
● Production incidentPOST-MORTEMseverity: high

The Database Scheduling That Caused Hourly Timeouts

Symptom
Payment processing jobs consistently ran past their SLA window, causing timeouts and failed transactions. Each hour, the first few jobs finished fast, but the tail jobs accumulated delays.
Assumption
The team assumed 'earliest start time' would maximise throughput because jobs would start immediately.
Root cause
Greedy by earliest start time picks jobs that start early but may end late, blocking many shorter jobs that could have completed in between. The correct greedy for throughput is earliest finish time.
Fix
Replaced the scheduling algorithm with earliest finish time greedy. Sorted jobs by end time, selected non-overlapping jobs. Throughput improved by 40% and all jobs met SLA.
Key lesson
  • 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.
Production debug guideHow to detect when greedy is giving wrong results before it costs you money4 entries
Symptom · 01
Unexpectedly suboptimal results — system delivers fewer tasks, less profit, or longer routes than expected.
Fix
Implement a brute-force baseline for small input sizes (N ≤ 10) and compare outputs. Run it in a test environment with real data samples.
Symptom · 02
Greedy solution passes unit tests but fails integration tests with larger data.
Fix
Check if the problem has optimal substructure. If not, greedy cannot work. Prove or disprove the greedy choice property with a counterexample generator.
Symptom · 03
Production logs show decision quality degrades as input size grows.
Fix
Profile the decision rules. Use visualisation — plot the chosen vs. optimal decisions for small random instances. Look for patterns of local optimum traps.
Symptom · 04
After a data change (e.g., new coin denominations), greedy starts failing.
Fix
Re-validate the greedy choice property with the new data. Test with canonical coin check algorithm — if the coin system is not canonical, greedy fails.
★ Greedy Algorithm Debugging Cheat SheetQuick commands and checks to verify greedy correctness in production
Interval scheduling: too few activities selected
Immediate action
Check sort order — did you sort by end time or start time?
Commands
print('Sorted intervals:', sorted(intervals, key=lambda x: x.end))
Run brute-force with N<=10: max_activities_bruteforce(intervals)
Fix now
Change sort key from start to end time. Rerun.
Coin change: greedy gives more coins than optimal+
Immediate action
Check if coin system is canonical — test with known problematic amount (e.g., 6 with coins 1,3,4)
Commands
java io.thecodeforge.algorithms.greedy.CoinChangeChecker --coins 1,3,4 --amount 6
Compare with DP result: java io.thecodeforge.algorithms.dp.CoinChange --coins 1,3,4 --amount 6
Fix now
If not canonical, replace greedy with DP implementation.
Huffman coding: compressed output larger than input+
Immediate action
Verify frequency counts are correct — single character frequencies not zero
Commands
wc -c input.txt && java io.thecodeforge.algorithms.greedy.HuffmanEncoder input.txt output.huf
Check tree shape: if all characters have equal frequency, Huffman gives flat tree.
Fix now
If frequencies are uniform, Huffman offers no compression; consider run-length encoding.
Activity selection: overlapping activities selected+
Immediate action
Check condition current.start >= lastSelected.end — did you use <= or < incorrectly?
Commands
grep 'start.*end' IntervalScheduler.java
Add assert: assert current.start >= lastSelected.end : 'Overlap found'
Fix now
Fix comparison operator. Redeploy.
Greedy vs Dynamic Programming
CriterionGreedyDynamic Programming
Time ComplexityO(N log N) or O(N)O(N²) or O(N * W)
Space ComplexityO(1) extra or O(N) for sortingO(N * W) typical
Correctness Proof RequiredYes — greedy choice property + optimal substructureYes — optimal substructure only
BacktrackingNone — commits to each choiceMemoization/tabulation, revisits subproblems
RobustnessFragile — tiny change in problem constraints can break greedyRobust — handles a wide range of constraints
Example ProblemsInterval scheduling, Huffman, DijkstraKnapsack, Edit Distance, LCS
Production RiskHigh if proof skippedLow if subproblem space manageable

Key takeaways

1
Greedy makes the locally optimal choice at each step
no backtracking.
2
Greedy is correct when the problem has the greedy choice property
local optimal = global optimal.
3
Always prove correctness before trusting a greedy solution
or test with edge cases.
4
Interval scheduling (sort by end time) and Dijkstra are classic correct greedy algorithms.
5
Coin change with arbitrary denominations requires dynamic programming, not greedy.

Common mistakes to avoid

5 patterns
×

Using earliest start time for interval scheduling

Symptom
The algorithm selects fewer activities than optimal, often leaving large gaps of unused time between long early activities.
Fix
Sort by finish time instead. Verify with a counterexample like [(1,10), (2,3), (4,5)] — earliest start picks only first, earliest finish picks last two.
×

Assuming greedy works for coin change with arbitrary denominations

Symptom
Greedy returns more coins than necessary for certain amounts. E.g., with coins [1,3,4], amount 6 gives 3 coins (4+1+1) vs optimal 2 (3+3).
Fix
Implement a canonical coin system check. If not canonical, use dynamic programming. Test with all amounts up to a max value.
×

Forgetting to prove greedy choice property before deployment

Symptom
Silent suboptimal results in production that are hard to detect because the algorithm 'works' but not optimally. Gradual degradation of KPIs.
Fix
Integrate a counterexample generator into CI. For any greedy implementation, run a brute-force verifier for small N (≤10) on random inputs and assert equality.
×

Using greedy for weighted interval scheduling

Symptom
Greedy picks intervals with earliest finish time regardless of weight, missing high-value but slightly longer intervals. Profit is suboptimal.
Fix
Use dynamic programming for weighted interval scheduling. The greedy choice property does not hold when intervals have weights.
×

Applying greedy to the 0/1 Knapsack problem (non-fractional)

Symptom
Greedy by value/weight ratio fails because taking a fraction of an item is not allowed. Greedy may choose a high-density item that precludes two lower-density items that together have more total value.
Fix
Use DP for 0/1 Knapsack. Greedy only works for the Fractional Knapsack problem (items can be split).
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the 'Greedy Choice Property' and how does it differ from 'Optima...
Q02SENIOR
Explain how the 'Jump Game' (LeetCode 55) can be solved using a greedy a...
Q03SENIOR
Why does the greedy approach for the 0/1 Knapsack problem fail while it ...
Q04SENIOR
Prove why sorting by 'Earliest Start Time' is a sub-optimal greedy strat...
Q05SENIOR
In Huffman Coding, what is the greedy choice being made at each step of ...
Q01 of 05SENIOR

What is the 'Greedy Choice Property' and how does it differ from 'Optimal Substructure'?

ANSWER
The greedy choice property means that a globally optimal solution can be arrived at by making a locally optimal (greedy) choice. Optimal substructure means that an optimal solution to the problem contains optimal solutions to its subproblems. Both are required for a greedy algorithm to be correct. Optimal substructure alone is not enough — many DP problems have it but greedy fails.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
How do you prove a greedy algorithm is correct?
02
What is the difference between greedy and dynamic programming?
03
When should I definitely NOT use a Greedy algorithm?
04
Is Dijkstra's algorithm greedy?
05
What is a counterexample to greedy coin change with denominations 1, 3, 4?
06
Can greedy algorithms be used in machine learning?
🔥

That's Greedy & Backtracking. Mark it forged?

6 min read · try the examples if you haven't

Previous
Cuckoo Hashing
1 / 13 · Greedy & Backtracking
Next
Activity Selection Problem