Interval Scheduling: Code Templates in 6 Languages
If you have not read the concept guide yet, start there for the intuition and complexity analysis. This page gives you the code you can memorize and adapt during an interview. The main template handles maximum non-overlapping selection. The variations cover merging, resource counting, and the weighted case where the greedy rule no longer works.
Main Template: Activity Selection (Earliest Finish Time)
The core interval scheduling template. Sort by end time, then greedily take every interval that does not overlap the last one taken. This is the engine behind Non-overlapping Intervals and Meeting Rooms , and it is the answer to most “maximum number of tasks” questions.
function activitySelection(intervals) {
// Sorting by end time puts the earliest-finishing interval
// first, which leaves the most room for everything after it.
intervals.sort((a, b) => a.end - b.end);
const selected = [];
let lastEnd = -Infinity;
for (const interval of intervals) {
// Half-open semantics: an interval that starts exactly
// when the last one ends does not conflict.
if (interval.start >= lastEnd) {
selected.push(interval);
lastEnd = interval.end;
}
}
return selected;
}Code Breakdown
Key Variables
lastEnd: the end time of the most recently accepted interval. Every later decision compares against this single value, which is the entire state the loop needs.selected: the result list of accepted intervals.- The sort: sorting by end time is what makes the greedy choice safe. Without it, an interval that ends late could be accepted early and block several short intervals that end sooner.
Visual Mechanism
stateDiagram-v2
[*] --> Sort: Sort intervals by end time
Sort --> Scan: Loop over intervals
Scan --> Keep: start >= lastEnd
Scan --> Drop: start < lastEnd
Keep --> Update: record interval end
Update --> Scan: next interval
Drop --> Scan: next interval
Scan --> Done: no intervals left
Done --> [*]
Critical Sections
The initialization sets lastEnd to negative infinity so the first interval is always accepted. Any real start time is larger than it.
The acceptance check is where the boundary semantics live. start >= lastEnd accepts intervals that merely touch, which matches half-open intervals. Problems that treat touching as overlap use > instead. This is the one line you change when a problem changes its overlap rule.
The advance step updates lastEnd only when an interval is accepted. A skipped interval must not move it, because a rejected interval is not part of the schedule and cannot block later ones.
Variations
1. Merge Intervals
When intervals must be combined instead of selected, sort by start time instead. After that, each interval can only overlap the last merged one.
Use this for Merge Intervals .
function merge(intervals) {
if (intervals.length === 0) return [];
// Sorting by start time means any overlap with a previous
// interval always involves the last entry in the result.
intervals.sort((a, b) => a.start - b.start);
const merged = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const last = merged[merged.length - 1];
const current = intervals[i];
if (current.start <= last.end) {
// Overlap: widen the merged interval to cover both.
last.end = Math.max(last.end, current.end);
} else {
// No overlap: the current interval starts a new block.
merged.push(current);
}
}
return merged;
}
graph LR
I["[1,3], [2,6], [8,10], [15,18]"] --> S["Sort by start time"]
S --> M["[2,6] overlaps [1,3], widen to [1,6]"]
M --> A["[8,10] is separate, add"]
A --> B["[15,18] is separate, add"]
B --> R["Result: [1,6], [8,10], [15,18]"]
2. Meeting Rooms II (Minimum Resources)
When the question is how many resources are needed, sort the starts and ends separately and walk both with two pointers. Every start that finds no free end opens a new room.
Use this for Meeting Rooms II .
function minMeetingRooms(intervals) {
// Sorting starts and ends independently turns the problem
// into merging two timelines, one for meetings beginning
// and one for meetings ending.
const starts = intervals.map(i => i.start).sort((a, b) => a - b);
const ends = intervals.map(i => i.end).sort((a, b) => a - b);
let rooms = 0;
let endIdx = 0;
for (const start of starts) {
if (start < ends[endIdx]) {
// A meeting starts before the earliest room frees
// up, so a new room is needed.
rooms++;
} else {
// The earliest-ending meeting is done, so its room
// can be reused.
endIdx++;
}
}
return rooms;
}
graph LR
I["Meetings: 0-30, 5-10, 15-20"] --> S["starts: 0, 5, 15 and ends: 10, 20, 30"]
S --> P1["start 0 < end 10, open a room, count 1"]
P1 --> P2["start 5 < end 10, open a room, count 2"]
P2 --> P3["start 15 >= end 10, first room is free, reuse"]
P3 --> R["Answer: 2 rooms"]
3. Weighted Interval Scheduling
When intervals carry a value and you must maximize the total, the greedy rule fails and dynamic programming takes over. Sort by end time, then for each interval choose between skipping it and taking it plus the best compatible schedule that ends before it starts. This runs in
function weightedIntervalScheduling(intervals) {
// intervals is an array of [start, end, value].
// Sorting by end time lets each interval find its compatible
// predecessors with binary search.
intervals.sort((a, b) => a[1] - b[1]);
const n = intervals.length;
const ends = intervals.map(interval => interval[1]);
// dp[i] is the best total value using only the first i intervals.
const dp = new Array(n + 1).fill(0);
// First index whose end time is greater than target.
const bisectRight = (arr, target) => {
let lo = 0, hi = arr.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (arr[mid] <= target) lo = mid + 1;
else hi = mid;
}
return lo;
};
for (let i = 1; i <= n; i++) {
const [start, , value] = intervals[i - 1];
// Only intervals that finish at or before start can
// precede this one in a valid schedule.
let k = bisectRight(ends, start);
if (k > i - 1) k = i - 1; // an interval cannot precede itself
const take = value + dp[k];
const skip = dp[i - 1];
dp[i] = Math.max(take, skip);
}
return dp[n];
}
graph TD
A["Sort intervals by end time"] --> B{"For each interval"}
B --> C["Skip: keep the best value so far"]
B --> D["Take: value plus the best compatible schedule before it"]
C --> E["dp[i] holds the larger of the two"]
D --> E
Now head to the practice problems to apply these templates to real interview questions.