Coding Interview Patterns Module 13 – Heaps and top-K
Scheduling with heaps
The fewest rooms for overlapping meetings, and the order of a printer that runs the shortest job first: both problems, their brute force and the signal.
What you will learn
- Count the rooms or halls that overlapping intervals need with a min-heap of end times
- Prove that the heap opens the fewest halls that any schedule can use
- Simulate a printer that always runs the shortest waiting job, idle gaps included
- Pick contracts that grow a balance, and argue why the greedy choice is safe
- Compare the heap with other ways of counting the peak overlap, by result and by cost
Before you start
What you’ll learn · Before you start
What you will learn
- Count the rooms or halls that overlapping intervals need with a min-heap of end times
- Prove that the heap opens the fewest halls that any schedule can use
- Simulate a printer that always runs the shortest waiting job, idle gaps included
- Pick contracts that grow a balance, and argue why the greedy choice is safe
- Compare the heap with other ways of counting the peak overlap, by result and by cost
Before you start
On this page
To find the fewest rooms that a set of meetings needs, the problem often called meeting rooms II, sort the meetings by start time and keep a min-heap of the end times of the rooms in use. When a meeting starts, only the room that frees up first matters: if it has already emptied, the meeting takes it; otherwise every room is busy and a new one opens. That is O(n log n) for n meetings. A heap decides other schedules the same way, such as a printer that always runs the shortest waiting job. Both problems follow, here with exam halls, with their brute force.
Two scheduling problems
Problem A: the fewest exam halls
An exam board must seat n exams. Each exam runs over the half-open interval [start, end) in minutes, so an exam that ends at minute 600 and one that starts at 600 may share a hall. Two exams that overlap need different halls. Return the fewest halls that hold every exam. Limits: 1 ≤ n ≤ 200,000 and 0 ≤ start < end ≤ 10⁹.
[(540, 660), (600, 720), (660, 780), (720, 840), (610, 640)]needs 3 halls: at minute 610 three exams run at once.[(0, 30), (30, 60), (60, 90)]needs 1 hall, because each exam ends as the next one starts.
Problem B: the order of a shortest-job-first printer
Job i arrives at minute arrival[i] and needs duration[i] minutes. The printer prints one job at a time and never interrupts one. Whenever it has no job, it starts the waiting job with the shortest duration, the lower index on a tie, and when nothing waits it stays idle until the next arrival. Return the jobs in printing order. Limits: up to 100,000 jobs, arrivals from 0 to 10⁹, durations from 1 to 10⁹.
For the jobs [(0, 5), (1, 2), (2, 1), (3, 3), (20, 1)], as (arrival, duration), the order is 0, 2, 1, 3, 4: job 0
runs until minute 5, then the shortest of the three waiting jobs, job 2, and the printer idles from 11 to 20.
The brute force, and why it fails at these sizes
For problem A, the most exams running at one moment is the answer, and that peak happens at some exam’s start, so count the exams that run at each start: n² = 4 × 10¹⁰ checks at 200,000 exams. For problem B, scan every job at every decision for the shortest waiting one: n² = 10¹⁰ looks at 100,000 jobs.
# Both problems the slow way, with the work counted: halls by counting the exams that run at each exam's start, and
# the printer by scanning every job at every decision.
def halls_by_counting(exams):
"""Problem A: the most exams running at one moment, checked at every start. Exams are half-open [start, end)."""
best, checks = 0, 0
for s, _ in exams:
running = 0
for a, b in exams:
checks += 1
running += a <= s < b
best = max(best, running)
return best, checks
def order_by_scanning(jobs):
"""Problem B: at each decision, scan every unprinted job for the shortest one that has arrived."""
done, order, t, looks = [False] * len(jobs), [], 0, 0
while len(order) < len(jobs):
pick = None
for i, (arrival, duration) in enumerate(jobs):
looks += 1
if not done[i] and arrival <= t and (pick is None or duration < jobs[pick][1]):
pick = i # strict <: the lower index stays on a tie
if pick is None: # nothing waiting: idle until the next arrival
t = min(a for i, (a, _) in enumerate(jobs) if not done[i])
continue
done[pick] = True
order.append(pick)
t += jobs[pick][1]
return order, looks
print("A:", *halls_by_counting([(540, 660), (600, 720), (660, 780), (720, 840), (610, 640)]), "checks")
print("A:", *halls_by_counting([(0, 30), (30, 60), (60, 90)]), "checks")
print("B:", *order_by_scanning([(0, 5), (1, 2), (2, 1), (3, 3), (20, 1)]), "looks")
n = 200_000
print(f"A at {n:,} exams: n * n = {n * n:,} checks")
n = 100_000
print(f"B at {n:,} jobs: n * n = {n * n:,} looks, one scan of every job per job printed") Output
A: 3 25 checks A: 1 9 checks B: [0, 2, 1, 3, 4] 30 looks A at 200,000 exams: n * n = 40,000,000,000 checks B at 100,000 jobs: n * n = 10,000,000,000 looks, one scan of every job per job printed
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
What to notice
Each decision needs only the smallest item
Text description of the diagram
The upper half shows problem A. The exam [660, 780) starts at minute 660 while three halls are in use. They empty at minutes 640, 660 and 720; the hall that frees at 640 is drawn with a double border. An arrow labelled "look at the earliest only" leads to the conclusion: the earliest, 640, is at or before 660, so a hall has emptied and no new hall opens.
A dashed arrow labelled "the same question in another problem" leads to the lower half, which shows problem B. The printer finishes a job at minute 5, and three jobs are waiting: job 2 needs 1 minute, job 1 needs 2 minutes and job 3 needs 3 minutes; job 2 is drawn with a double border. An arrow labelled "look at the shortest only" leads to the conclusion: the shortest, job 2, prints next.
- When an exam starts, only the hall that frees first matters. If even that one is still busy, every hall is.
- When the printer finishes a job, only the shortest waiting job matters. The others wait for later decisions.
Both problems repeatedly ask for the smallest item of a collection that grows and shrinks: the earliest end time among the halls in use, the shortest duration among the waiting jobs. That is a min-heap’s job.
Halls, step by step: a heap of end times Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why no schedule can use fewer halls: a proof
- A dry run of both heaps
- Three ways to count halls, measured on 200,000 exams
- The printer, step by step: arrivals in order, a heap of waiting jobs
- Why the printer’s order is right, and what it costs
- Measured: scanning against the heap for the printer
- The code in five languages
- Edge cases and common bugs, each found by a drill
- Variants: numbered halls, platforms and contracts
- Exercise: the order in which one printer handles its jobs
- The order in which one printer handles its jobs
- A practice set with sample answers
- Check yourself
- Interview questions
- In the fewest-rooms problem, why does the heap hold end times, and why only the smallest one is checked when a meeting starts?
- Prove that the heap of end times opens the fewest rooms possible. Then a colleague suggests sorting the start times and the end times separately and sweeping them with two pointers. Is that correct, and when would you prefer it?
- A shortest-job-first printer simulation passes the examples but fails hidden tests with idle gaps and equal durations. Walk through what the loop must do after an idle gap and on a tie, and how you would test it.
- The office now has k identical printers. Each free printer takes the shortest waiting job, the lower printer number first when several are free. Return the printer and start time of every job. Design the simulation and give its cost.
- Key takeaways
7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 25 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- heapq: Heap queue algorithm (Python Software Foundation)
- Java SE 25 API: PriorityQueue (Oracle)
- Java SE 25 API: Arrays.sort with a Comparator (Oracle)
- C++ working draft N4950: Class template priority_queue [priority.queue] (ISO/IEC JTC1/SC22/WG21 (HTML rendering of the working draft))
- C++ working draft N4950: Relational operators of pair [pairs.spec] (ISO/IEC JTC1/SC22/WG21 (HTML rendering of the working draft))
- Package container/heap (The Go Authors)
- The Go Programming Language Specification: numeric types (The Go Authors)
- ECMAScript 2026 Language Specification: Number.MAX_SAFE_INTEGER (Ecma International)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress