Greedy Algorithms · Greedy Fundamentals

Deadline Scheduling Greedy

Processing tasks from most to least valuable, slotting each into the latest still-open time slot at or before its deadline - preserving earlier slots for tasks that can't be scheduled any later.

  • 5 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: accept, then drop the worst (greedy with regret)
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Jobs each take one unit of time, have a deadline and pay a profit only if finished by it. With one machine, which jobs should run, and when? Trying every subset and order is hopeless; two greedy rules together solve it exactly.

This skill covers the two rules, most profitable first and each job in its latest free slot. It also covers the "accept, then drop the worst" form that handles jobs of different lengths, and how to find free slots quickly when deadlines are large.

We'll go from simple to more technical:

  1. The pattern: profit order picks which jobs run, and the latest free slot picks when.
  2. A second flavor: sort by deadline, accept, and drop the worst from a heap (Course Schedule III).
  3. Where deadline scheduling shows up (batch windows, deliverables, admission control).
  4. What breaks it: earliest slots, deadline-only order, slow slot scans, durations with profits.

The approach

Java

int[] jobSequencing(int[] deadlines, int[] profits) {
    Integer[] order = new Integer[deadlines.length];
    for (int i = 0; i < order.length; i++) order[i] = i;
    Arrays.sort(order, (a, b) -> Integer.compare(profits[b], profits[a]));   // most profitable first
    int maxDeadline = Arrays.stream(deadlines).max().getAsInt();
    boolean[] taken = new boolean[maxDeadline + 1];                           // slots 1..maxDeadline
    int count = 0, total = 0;
    for (int i : order) {
        for (int slot = deadlines[i]; slot >= 1; slot--) {                   // latest free slot first
            if (!taken[slot]) {
                taken[slot] = true;
                count++;
                total += profits[i];
                break;
            }
        }
    }
    return new int[]{count, total};
}

Python

def job_sequencing(deadlines: list[int], profits: list[int]) -> list[int]:
    max_deadline = max(deadlines)
    taken = [False] * (max_deadline + 1)                     # slots 1..max_deadline
    count = total = 0
    for p, d in sorted(zip(profits, deadlines), reverse=True):   # most profitable first
        for slot in range(d, 0, -1):                         # latest free slot first
            if not taken[slot]:
                taken[slot] = True
                count += 1
                total += p
                break
    return [count, total]

Profit order decides which jobs to keep; the latest-slot rule decides where to put them. Placing a job as late as its deadline allows keeps early slots free for jobs with tighter deadlines. The earliest free slot fails: with deadlines [2, 1] and profits [100, 50], the 100-job takes slot 1 and the 50-job has nowhere to go - 100 instead of 150.

The inner scan is the weak point: when many deadlines are large, each placement can walk past many taken slots - about 5 * 10^9 steps for 10^5 jobs all due at time 10^5. A union-find "next free slot at or before d" (after using slot s, link it to s - 1) makes each placement nearly O(1).

A second flavor: accept, then drop the worst (greedy with regret)

Sort by deadline instead and keep a heap of what you've accepted. Take each job tentatively; if you now hold more jobs than the deadline allows, drop the least profitable one. For jobs with different durations - Course Schedule III: take the most courses, each with a duration and a last day - the same idea drops the longest course:

Java

int scheduleCourse(int[][] courses) {                     // each course is [duration, lastDay]
    Arrays.sort(courses, (a, b) -> Integer.compare(a[1], b[1]));   // by last day
    PriorityQueue<Integer> taken = new PriorityQueue<>(Comparator.reverseOrder());   // longest on top
    int time = 0;
    for (int[] c : courses) {
        time += c[0];
        taken.add(c[0]);
        if (time > c[1]) time -= taken.poll();           // over the deadline: drop the longest course
    }
    return taken.size();
}

Python

import heapq


def schedule_course(courses: list[list[int]]) -> int:        # each course is [duration, last_day]
    taken = []                                   # heapq is a min-heap: store -duration for "longest on top"
    time = 0
    for duration, last_day in sorted(courses, key=lambda c: c[1]):   # by last day
        time += duration
        heapq.heappush(taken, -duration)
        if time > last_day:
            time += heapq.heappop(taken)         # over the deadline: drop the longest course
    return len(taken)

Dropping the longest accepted course restores feasibility while giving back the most time, and the count is never worse than skipping the current course. Simply skipping a course that doesn't fit is wrong: courses [5, 5], [4, 6], [2, 6] fit 2 (the last two), but skipping keeps [5, 5] and fits only 1. In Python, heapq's heappush/heappop are a min-heap, so push -duration and negate what you pop - forgetting drops the shortest course instead (Python 3.14 adds heapq.heappush_max/heappop_max; negating works on every version).

Where you'll see it

Scheduling paid batch jobs into a fixed number of nightly windows, choosing which deliverables fit before their due dates, and admission control ("accept the request, and if the queue can't meet its deadlines, evict the least valuable one") all use these two greedy forms.

Which tool for which job?

You needUse
Most profit, unit-time jobsprofit order, latest free slot
The same, with large deadlinesunion-find over free slots, or the deadline-sorted heap
Most jobs of different lengths on timedeadline order, drop the longest from a max-heap
Most profit, jobs of different lengthsDP over time (no greedy is exact)
Every job runs, smallest worst latenessearliest due date first
Every job runs, smallest average finish timeshortest job first

Common mistakes

MistakeWhat happensDo this instead
Earliest free slotdeadlines [2, 1], profits [100, 50] earn 100 instead of 150the latest free slot at or before the deadline
Deadline order, no profitsa cheap early job is kept over a valuable oneprofit order, or drop the cheapest from a heap
Skipping a course that doesn't fit[5, 5], [4, 6], [2, 6] fits 1 instead of 2add it, then drop the longest
Naive slot scan at scaleO(n * maxDeadline) stepsunion-find or the heap method
heapq as a max-heapdrops the shortest coursepush -duration

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why does each job go into its latest free slot rather than its earliest?

Show answer

Later jobs (lower profit) may have tighter deadlines and need the early slots. Placing each job as late as its own deadline allows keeps those slots free. Deadlines [2, 1] with profits [100, 50]: latest slot schedules both for 150, earliest slot only 100.

2. In Course Schedule III, why drop the longest course taken so far instead of skipping the current one?

Show answer

Either choice keeps the count the same, but removing the longest course frees the most time for later courses. Skipping the current course can keep a long early course that blocks two shorter ones: [5, 5], [4, 6], [2, 6] fits 2 with the heap and 1 by skipping.

3. What does union-find speed up in job sequencing?

Show answer

Finding the latest free slot at or before a deadline. Each used slot points to the one before it, so a lookup jumps straight past runs of taken slots instead of scanning them one by one.

Key takeaways

  • Unit jobs: highest profit first, each into its latest free slot at or before its deadline.
  • Earliest-slot placement and deadline-only order both fail.
  • Union-find finds free slots fast; the naive scan is O(n * maxDeadline).
  • Greedy with regret: sort by deadline, accept, and drop the worst from a heap when a deadline breaks.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Most profit, unit jobsprofit order, latest free slot
Large deadlinesunion-find, or the deadline heap
Most jobs on time, different lengthsMoore-Hodgson (drop the longest)
Most profit, different lengthsDP over time
Smallest worst latenessearliest due date
Smallest average finishshortest first

Snippets worth knowing by heart

Java

int find(int s) {                                  // latest free slot <= s, 0 if none
    while (parent[s] != s) s = parent[s] = parent[parent[s]];
    return s;
}
// place a job due at d: int s = find(d); if (s > 0) { parent[s] = s - 1; total += p; }

Python

import heapq
h = []                                             # unit jobs: the deadline-sorted heap method
for d, p in sorted(jobs):
    heapq.heappush(h, p)
    if len(h) > d:
        heapq.heappop(h)                           # drop the least profitable
best = sum(h)

The traps

  • Earliest free slot: an early job takes a slot a tighter job needed. Use the latest free slot at or before the deadline.
  • Deadline order without profits: a cheap early job beats a valuable one. Go by profit, or drop the cheapest from a heap.
  • Skipping instead of dropping the longest: fewer courses fit. Add the course, then drop the longest if over.
  • Slot scans at large deadlines: O(n * maxDeadline) steps. Use union-find or the heap method.
  • heapq is a min-heap: it drops the shortest course instead of the longest. Push -duration.

Staff-level points, one line each

  • Feasible sets of unit jobs form a matroid, so profit-first is exact; it matched brute force on 300 cases, as did the heap method.
  • Fits if and only if, for every t, at most t jobs are due by t.
  • 30,000 jobs due at 30,000: scan 450,015,000 steps, union-find 59,999.
  • Course Schedule III is Moore-Hodgson (1968); it matched brute force on 300 sets.
  • Durations plus profits is weakly NP-hard: drop-the-cheapest missed in 40 of 300 cases.
  • Earliest due date minimizes the worst lateness and shortest-first the average finish time; each missed the other's goal in about 80% of cases.

Say it in an interview

  • "Profit order decides which jobs run; the latest free slot decides when."
  • "Unit jobs with deadlines form a matroid, so the weight-ordered greedy is optimal."
  • "For large deadlines I'd use union-find over slots, or a heap sorted by deadline."
  • "With durations and profits there's no exact greedy, so I'd use a DP over time."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Deadline Scheduling Greedy free

Job Sequencing Problem

MediumProCoding problemJava · Python

You are given n jobs, each with a deadline and a profit. Each job takes exactly 1 unit of time, and only one job can run at a time. A job earns its profit only if it's completed at or before its deadline. Return [jobsDone, totalProfit] for a schedule that maximizes total profit.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Course Schedule III

HardProCoding problemJava · Python

Each course is [duration, lastDay]: it takes duration consecutive days and must be finished by day lastDay. You take courses one at a time, starting on day 0. Return the maximum number of courses you can finish.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Deadline Scheduling Greedy free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime