Interval Problems — The Complete Guide
Master interval manipulation from first principles. Learn to merge, insert, and schedule intervals, recognize overlap patterns instantly, and confidently solve every interval-based interview question.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
An interval is just a pair [start, end] representing a range — a meeting from 9am to 10am, a booking from Monday to Wednesday, a range of IP addresses. Interval problems ask you to reason about how these ranges relate to each other: do they overlap? Can they be merged? Where are the gaps? How many overlap at the same time?
The fundamental challenge is that intervals can overlap in messy ways. Given a random list of intervals, comparing every pair is O(n²). But here's the key insight: if you sort intervals by start time, overlapping intervals become adjacent in the sorted order. This turns a chaotic comparison problem into a clean linear scan.
That's the entire pattern: sort by start → sweep left to right → make greedy decisions. Every interval problem is a variation of this.
Analogies to Build Intuition
Merging Calendar Events
You have a messy calendar with overlapping meetings. To clean it up, you sort all meetings by start time, then walk through them: if the next meeting starts before the current one ends, they overlap — merge them into one longer block. If not, it's a separate meeting. That's Merge Intervals.
Hotel Room Booking
A hotel needs to know the maximum number of guests at any time. Each booking is an interval [check-in, check-out]. Sort all check-ins and check-outs on a timeline, then sweep through: +1 at each check-in, -1 at each check-out. The running total tells you how many rooms are occupied. That's the sweep line technique.
Popping Balloons on a Number Line
Balloons are placed at intervals on a number line. You want to pop as many as possible with the fewest arrows. Sort balloons by end point, shoot at the earliest ending balloon — this arrow also pops any balloon that overlaps with it. That's the greedy interval scheduling approach.
What kind of problems does it solve?
Interval techniques are your go-to when:
- You need to merge overlapping intervals into non-overlapping ones
- You need to insert a new interval into a sorted list and merge if needed
- You need to find the maximum number of overlapping intervals at any point
- You need to determine if a person can attend all meetings (no overlaps)
- You need to find the minimum number of resources (rooms, servers) to handle all intervals
- You need to remove minimum intervals to eliminate all overlaps
- You need to find free time / gaps between intervals
The Two Overlap Conditions — Know These Cold
Two intervals [a, b] and [c, d] (where a ≤ b and c ≤ d): OVERLAP if: a <= d AND c <= b (one starts before the other ends, AND vice versa) NO OVERLAP if: b < c OR d < a (one ends before the other starts) After sorting by start time (a ≤ c guaranteed): OVERLAP if: c <= b (next starts before current ends) NO OVERLAP: c > b (next starts after current ends) This simplification after sorting is why we ALWAYS sort first.
The core insight
Interval problems are sorting problems in disguise. Once sorted by start time, overlapping intervals are always adjacent. You never need to look backwards. This turns O(n²) pairwise comparison into O(n log n) sort + O(n) scan = O(n log n) total.
Pattern Recognition
Interval problems are usually easy to spot — the input is literally a list of [start, end] pairs. The real skill is recognizing whichinterval technique to apply.
Keywords & Signals in the Problem Statement
Use Interval techniques when you see...
- ✅"Merge overlapping intervals"
- ✅"Insert interval" into a list of non-overlapping intervals
- ✅"Meeting rooms" — can a person attend all? How many rooms needed?
- ✅"Minimum number of arrows / points to cover all intervals"
- ✅"Remove minimum intervals to make non-overlapping"
- ✅"Free time" or "gaps" between schedules
- ✅"Maximum number of events at the same time"
- ✅Input is a list of [start, end] pairs
- ✅"Booking" or "scheduling" problems
- ✅"Overlapping" appears in the problem description
Don't use Interval techniques when...
- ❌The ranges are on a 2D grid (might need sweep line + segment tree)
- ❌You need subarray sums (use prefix sum or sliding window)
- ❌The 'intervals' are actually indices into an array (might be two pointers)
- ❌The problem involves circular ranges without clear linearization
- ❌You need to find a specific value within ranges (might be binary search)
- ❌The intervals represent edges in a graph (use graph algorithms)
Interval Techniques vs. Similar Patterns
| Pattern | When to Use | Key Difference |
|---|---|---|
| Interval Merge/Sort | Merge overlapping ranges, insert intervals, check conflicts | Sort by start → linear scan. Greedy decisions based on overlap |
| Sweep Line | Count max overlaps, find busy/free periods | Convert intervals to events (+1/-1), sort events, scan with running count |
| Greedy Scheduling | Maximize non-overlapping intervals, minimize removals/arrows | Sort by END time, greedily pick earliest-ending non-conflicting interval |
| Sliding Window | Contiguous subarray with a constraint | Window moves over array indices, not arbitrary [start, end] ranges |
| Difference Array | Apply range updates efficiently | Intervals represent updates to an array, not standalone ranges to merge |
Sort by Start vs Sort by End — When?
| Sort By | When | Why |
|---|---|---|
| Start time | Merge intervals, insert interval, meeting rooms, free time | You process intervals in chronological order and merge/extend as you go |
| End time | Max non-overlapping intervals, min arrows, min removals | Greedy: pick the interval that ends earliest → leaves most room for future intervals |
The sorting question
Before coding any interval problem, ask: "Should I sort by start or end?" If you're merging or scanning chronologically → sort by start. If you're greedily selecting non-overlapping intervals → sort by end. Getting this wrong leads to incorrect greedy choices.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using the classic example: Merge Intervals — given a list of intervals, merge all overlapping intervals and return the non-overlapping result.
Brute Force — Compare Every Pair
For each interval, check if it overlaps with any other interval. If it does, merge them and restart the scan (because the merged interval might now overlap with something else). Repeat until no more merges are possible.
function merge(intervals: number[][]): number[][] { const result = [...intervals]; let merged = true; while (merged) { merged = false; for (let i = 0; i < result.length; i++) { for (let j = i + 1; j < result.length; j++) { // Check overlap: a.start <= b.end AND b.start <= a.end if (result[i][0] <= result[j][1] && result[j][0] <= result[i][1]) { // Merge: take min start, max end result[i] = [ Math.min(result[i][0], result[j][0]), Math.max(result[i][1], result[j][1]) ]; result.splice(j, 1); // remove j merged = true; break; } } if (merged) break; // restart outer loop } } return result; } // Problem: O(n²) per pass, potentially O(n) passes = O(n³) worst case. // We're comparing every pair and restarting after each merge.
Key Observation — Sorting Makes Overlaps Adjacent
If we sort intervals by start time, any interval that overlaps with the current one MUST be the next one in sorted order. We never need to look backwards or restart. This is because after sorting, if interval B doesn't overlap with A, then nothing after B can overlap with A either (they all start even later).
Unsorted: [[1,3], [8,10], [2,6], [15,18]] → Which pairs overlap? Need to check all 6 pairs. Sorted by start: [[1,3], [2,6], [8,10], [15,18]] → [1,3] and [2,6] overlap (2 ≤ 3) → merge to [1,6] → [1,6] and [8,10]? No (8 > 6) → done with [1,6] → [8,10] and [15,18]? No (15 > 10) → done Result: [[1,6], [8,10], [15,18]] After sorting, we only compare ADJACENT intervals. One pass. No restarts. O(n) after the O(n log n) sort.
Optimal — Sort + Single Pass Merge
Sort by start time. Initialize result with the first interval. For each subsequent interval: if it overlaps with the last interval in result (its start ≤ result's last end), extend the end. Otherwise, it's a new non-overlapping interval — push it to result.
function merge(intervals: number[][]): number[][] { // Step 1: Sort by start time intervals.sort((a, b) => a[0] - b[0]); const result: number[][] = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const last = result[result.length - 1]; const curr = intervals[i]; if (curr[0] <= last[1]) { // Overlap → extend the end of the last merged interval last[1] = Math.max(last[1], curr[1]); } else { // No overlap → start a new interval result.push(curr); } } return result; } // O(n log n) for sort + O(n) for scan = O(n log n) total. // Each interval is processed exactly once. Clean and optimal.
Why Does Each Optimization Work?
Brute → Sorted
Sorting by start time guarantees that if interval B doesn't overlap with A, nothing after B can overlap with A either (they all start later). This eliminates the need for pairwise comparison and restarts.
Why a Single Pass Works
After sorting, we maintain a 'current merged interval' (the last one in result). Each new interval either extends it (overlap) or starts a new one (no overlap). We never need to go back because the sorted order guarantees all overlaps with the current interval are consecutive.
The General Principle
Almost every interval problem follows this pattern: sort to bring related intervals together, then make a single greedy pass. The sort is the key enabler — it turns a 2D comparison problem into a 1D scan.
The thinking process matters more than the code
In interviews, explain: "Unsorted intervals can overlap in any order, making pairwise comparison O(n²). But if I sort by start time, overlapping intervals become adjacent. I can merge in a single pass by comparing each interval with the last merged one. This gives me O(n log n)."
Core Templates
Interval problems boil down to three core templates. Memorize these — every problem is a variation of one of them.
Template 1: Merge Overlapping Intervals
Sort by start. Walk through, extending the current interval when overlap is found. Used for merge intervals, insert interval, and finding gaps.
function mergeIntervals(intervals: number[][]): number[][] { intervals.sort((a, b) => a[0] - b[0]); // sort by start const merged: number[][] = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const last = merged[merged.length - 1]; const curr = intervals[i]; if (curr[0] <= last[1]) { // Overlap → extend end last[1] = Math.max(last[1], curr[1]); } else { // No overlap → new interval merged.push(curr); } } return merged; } // Variations: // - Insert interval: add new interval to list, then merge // - Find gaps: when curr[0] > last[1], the gap is [last[1], curr[0]] // - Intersection: instead of extending, take the overlap portion
Template 2: Sweep Line (Event-Based)
Convert each interval into two events: +1 at start, -1 at end. Sort all events, then sweep through with a running counter. Used for counting max overlaps, meeting rooms, and busy/free periods.
function maxOverlaps(intervals: number[][]): number { // Create events: [time, type] where +1 = start, -1 = end const events: [number, number][] = []; for (const [start, end] of intervals) { events.push([start, 1]); // interval starts events.push([end, -1]); // interval ends } // Sort by time. If same time, process ends (-1) before starts (+1) // (an interval ending at time T doesn't overlap with one starting at T) events.sort((a, b) => a[0] - b[0] || a[1] - b[1]); let active = 0; let maxActive = 0; for (const [, type] of events) { active += type; maxActive = Math.max(maxActive, active); } return maxActive; } // Variations: // - Meeting rooms II: maxActive = minimum rooms needed // - Find the time with max overlaps: track the time when maxActive updates // - Free time: periods where active === 0
Template 3: Greedy Interval Scheduling (Sort by End)
Sort by end time. Greedily select the interval that ends earliest and doesn't conflict with the last selected one. Used for maximizing non-overlapping intervals, minimum arrows, and minimum removals.
function maxNonOverlapping(intervals: number[][]): number { // Sort by END time (critical for greedy correctness) intervals.sort((a, b) => a[1] - b[1]); let count = 0; let lastEnd = -Infinity; for (const [start, end] of intervals) { if (start >= lastEnd) { // No conflict → select this interval count++; lastEnd = end; } // else: skip (overlaps with last selected) } return count; } // Why sort by end? The interval that ends earliest leaves the most // room for future intervals. This greedy choice is provably optimal. // // Variations: // - Min removals to make non-overlapping: n - maxNonOverlapping // - Min arrows to burst balloons: same logic (arrow at lastEnd pops all overlapping) // - Erase overlap intervals: count overlapping ones to remove
Quick Reference: Which Template?
| Problem Type | Sort By | Template |
|---|---|---|
| Merge overlapping intervals | Start time | Template 1 — extend or push |
| Insert interval into sorted list | Start time (already sorted) | Template 1 — find position, merge |
| Find gaps / free time | Start time | Template 1 — gaps between merged intervals |
| Max simultaneous overlaps | Event time | Template 2 — sweep line +1/-1 |
| Minimum meeting rooms | Event time | Template 2 — max active count |
| Max non-overlapping intervals | End time | Template 3 — greedy select earliest end |
| Min removals for no overlap | End time | Template 3 — n minus max non-overlapping |
| Min arrows to burst balloons | End time | Template 3 — greedy arrow placement |
The memory trick
Merging? → Sort by start, extend end. Counting overlaps? → Sweep line with +1/-1 events. Selecting/removing? → Sort by end, greedy pick. Three templates, three sort strategies. That's the entire pattern.
Variations & Sub-Patterns
The three templates cover the basics, but real interview problems add twists. Here are the most important variations.
| Variation | Core Technique | Classic Example |
|---|---|---|
| Merge Intervals | Sort by start → extend or push | Merge Intervals (LC 56) |
| Insert Interval | Three-phase: before, overlapping, after | Insert Interval (LC 57) |
| Interval Intersection | Two pointers on two sorted lists, take overlap portion | Interval List Intersections (LC 986) |
| Meeting Rooms (conflict check) | Sort by start, check if any consecutive pair overlaps | Meeting Rooms (LC 252) |
| Meeting Rooms II (min rooms) | Sweep line or min-heap of end times | Meeting Rooms II (LC 253) |
| Non-Overlapping Intervals | Sort by end, greedy select → removals = n - selected | Non-overlapping Intervals (LC 435) |
| Minimum Arrows | Sort by end, greedy arrow at earliest end | Minimum Number of Arrows (LC 452) |
| Employee Free Time | Merge all intervals across employees, find gaps | Employee Free Time (LC 759) |
Deep Dive: Insert Interval (Three-Phase Approach)
Insert Interval is trickier than Merge because the input is already sorted and non-overlapping. The cleanest approach splits the work into three phases: intervals entirely before the new one, intervals that overlap with it, and intervals entirely after it.
Problems mentioned above
function insert( intervals: number[][], newInterval: number[] ): number[][] { const result: number[][] = []; let i = 0; const n = intervals.length; // Phase 1: Add all intervals that end BEFORE newInterval starts while (i < n && intervals[i][1] < newInterval[0]) { result.push(intervals[i]); i++; } // Phase 2: Merge all overlapping intervals with newInterval while (i < n && intervals[i][0] <= newInterval[1]) { newInterval[0] = Math.min(newInterval[0], intervals[i][0]); newInterval[1] = Math.max(newInterval[1], intervals[i][1]); i++; } result.push(newInterval); // push the merged interval // Phase 3: Add all intervals that start AFTER newInterval ends while (i < n) { result.push(intervals[i]); i++; } return result; } // Phase 1: intervals[i][1] < newInterval[0] → entirely before // Phase 2: intervals[i][0] <= newInterval[1] → overlaps → merge // Phase 3: everything remaining → entirely after
Deep Dive: Interval List Intersections (Two Pointers)
Given two sorted lists of non-overlapping intervals, find their intersection. Use two pointers — one per list. At each step, compute the overlap (if any) and advance the pointer whose interval ends first.
function intervalIntersection( firstList: number[][], secondList: number[][] ): number[][] { const result: number[][] = []; let i = 0, j = 0; while (i < firstList.length && j < secondList.length) { const lo = Math.max(firstList[i][0], secondList[j][0]); const hi = Math.min(firstList[i][1], secondList[j][1]); if (lo <= hi) { result.push([lo, hi]); // valid intersection } // Advance the pointer whose interval ends first if (firstList[i][1] < secondList[j][1]) { i++; } else { j++; } } return result; } // Why advance the earlier-ending interval? It can't intersect with // anything else in the other list (the other list is sorted, so // future intervals start even later).
Meeting Rooms II — Two approaches
You can solve Meeting Rooms II with either sweep line (Template 2) or a min-heap. The heap approach: sort by start, maintain a min-heap of end times. For each meeting, if it starts after the earliest end → reuse that room (pop and push new end). Otherwise → need a new room (just push). Heap size at the end = min rooms needed.
Visual Walkthrough
Let's trace through three different interval techniques to see how each one works step by step.
Merge Intervals — Step by Step
Input: [[1,3], [8,10], [2,6], [15,18]] Step 1: Sort by start → [[1,3], [2,6], [8,10], [15,18]] Step 2: Initialize result = [[1,3]] i=1: curr=[2,6], last=[1,3] 2 ≤ 3? YES → overlap! Extend: last = [1, max(3,6)] = [1,6] result = [[1,6]] i=2: curr=[8,10], last=[1,6] 8 ≤ 6? NO → no overlap. Push [8,10] result = [[1,6], [8,10]] i=3: curr=[15,18], last=[8,10] 15 ≤ 10? NO → no overlap. Push [15,18] result = [[1,6], [8,10], [15,18]] Output: [[1,6], [8,10], [15,18]] Timeline visualization: [1---3] [2------6] [8--10] [15--18] ───────────────────────────────────── [1--------6] [8--10] [15--18] ← merged
Sweep Line (Meeting Rooms II) — Step by Step
Meetings: [[0,30], [5,10], [15,20]] Step 1: Create events [0, +1] — meeting 1 starts [30, -1] — meeting 1 ends [5, +1] — meeting 2 starts [10, -1] — meeting 2 ends [15, +1] — meeting 3 starts [20, -1] — meeting 3 ends Step 2: Sort by time (ends before starts if same time) [0, +1], [5, +1], [10, -1], [15, +1], [20, -1], [30, -1] Step 3: Sweep t=0: active = 0+1 = 1 maxActive = 1 t=5: active = 1+1 = 2 maxActive = 2 ★ t=10: active = 2-1 = 1 t=15: active = 1+1 = 2 maxActive = 2 t=20: active = 2-1 = 1 t=30: active = 1-1 = 0 Answer: 2 rooms needed (at times 5-10 and 15-20, two meetings overlap) Timeline: Room 1: [0=========================30] Room 2: [5===10] [15===20] ────────────────────────────────────── Active: 1 2 1 2 1 0
Greedy Scheduling (Non-Overlapping) — Step by Step
Input: [[1,2], [2,3], [3,4], [1,3]] Goal: Remove minimum intervals to make all non-overlapping. Step 1: Sort by END time → [[1,2], [2,3], [1,3], [3,4]] Step 2: Greedy select (pick earliest-ending non-conflicting) lastEnd = -∞ [1,2]: 1 ≥ -∞? YES → select. count=1, lastEnd=2 [2,3]: 2 ≥ 2? YES → select. count=2, lastEnd=3 [1,3]: 1 ≥ 3? NO → skip (overlaps with last selected) [3,4]: 3 ≥ 3? YES → select. count=3, lastEnd=4 Max non-overlapping = 3 Min removals = 4 - 3 = 1 (remove [1,3]) Why sort by end? [1,3] ends later than [2,3], so keeping [2,3] leaves more room for [3,4]. Earliest end = most room for future. Timeline: [1-2] [2-3] [3-4] ← selected (no overlaps) [1----3] ← removed (overlaps with [2,3]) ──────────────────
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different interval technique. Solve them in order.
LC 252. Meeting Rooms
Why this pattern:
The simplest interval problem: can a person attend all meetings? Sort by start time, check if any consecutive pair overlaps. If intervals[i].start < intervals[i-1].end → conflict.
Key idea: Sort by start. Scan pairs: if any meeting starts before the previous one ends, return false. This is just 'check if any intervals overlap' — the foundation for all interval problems.
LC 56. Merge Intervals
Why this pattern:
The canonical interval problem (Template 1). Sort by start, then sweep: if the current interval overlaps with the last merged one, extend the end. Otherwise, push a new interval.
Key idea: Sort by start. Maintain result array. For each interval: if curr.start ≤ last.end → overlap, extend last.end = max(last.end, curr.end). Else → push curr. One pass after sorting.
LC 57. Insert Interval
Why this pattern:
Three-phase approach on an already-sorted list. Phase 1: add intervals before the new one. Phase 2: merge all overlapping intervals with the new one. Phase 3: add intervals after.
Key idea: Walk through the sorted list. While intervals end before newInterval starts → add to result. While intervals overlap with newInterval → merge (extend newInterval). Add newInterval. Add remaining intervals.
LC 435. Non-overlapping Intervals
Why this pattern:
Greedy scheduling (Template 3). Find the maximum number of non-overlapping intervals, then removals = n - max. Sort by END time and greedily pick the earliest-ending non-conflicting interval.
Key idea: Sort by end. Greedily select: if curr.start ≥ lastEnd → select, update lastEnd. Count selected. Answer = n - count. Sorting by end is critical — it ensures the greedy choice is optimal.
LC 253. Meeting Rooms II
Why this pattern:
Sweep line (Template 2) or min-heap approach. Count the maximum number of overlapping meetings at any point in time — that's the minimum number of rooms needed.
Key idea: Sweep line: create +1 events at starts, -1 at ends. Sort events. Sweep with running count. Max count = answer. Alternatively: sort by start, use a min-heap of end times. If earliest end ≤ current start → reuse room (pop). Always push current end. Heap size = rooms.
LC 452. Minimum Number of Arrows to Burst Balloons
Why this pattern:
Greedy scheduling variant (Template 3). Each arrow is placed at a point. Sort balloons by end. Place arrow at the earliest end — it bursts all balloons that overlap with that point.
Key idea: Sort by end. For each balloon: if its start > lastArrow position → need a new arrow at this balloon's end. Otherwise → the existing arrow already covers it. Count arrows.
LC 986. Interval List Intersections
Why this pattern:
Two-pointer approach on two sorted interval lists. At each step, compute the overlap (max of starts, min of ends). If valid overlap, add it. Advance the pointer whose interval ends first.
Key idea: Two pointers i, j. Overlap = [max(a.start, b.start), min(a.end, b.end)]. If lo ≤ hi → valid intersection. Advance the pointer with the smaller end (it can't intersect with anything else in the other list).
Practice strategy
For each problem: (1) Identify which template applies (merge, sweep line, or greedy). (2) Determine the sort order (start or end). (3) Write the overlap condition. (4) After solving, write: "This is [template] because [I need to merge / count overlaps / select non-overlapping]."
Common Mistakes
These are the traps that catch people on interval problems. Learn them here so you don't learn them in an interview.
Sorting by start when you should sort by end (or vice versa)
You sort by start for a greedy scheduling problem. The greedy choice becomes incorrect — you might select a long interval that blocks many shorter ones.
✅Merging/scanning → sort by start. Greedy selection (max non-overlapping, min arrows) → sort by end. The sort order determines whether the greedy choice is optimal. Get this right before coding.
Off-by-one in overlap condition (< vs ≤)
You use curr.start < last.end when the problem considers touching intervals [1,2] and [2,3] as overlapping. Or you use ≤ when they shouldn't overlap.
✅Read the problem carefully: do intervals [1,2] and [2,3] overlap? If the problem says 'meetings at [1,2] and [2,3] don't conflict' → use strict <. If they do conflict → use ≤. This varies by problem.
Forgetting to use Math.max when extending the merged interval
You set last.end = curr.end instead of last.end = Math.max(last.end, curr.end). This fails when the current interval is entirely contained within the last one (e.g., [1,10] and [2,5]).
✅Always use Math.max(last.end, curr.end) when merging. The current interval might end before the last one — you need to keep the larger end. Draw [1,10] and [2,5] to see why.
Wrong event ordering in sweep line
When two events happen at the same time (one start, one end), you process the start before the end. This overcounts overlaps — an interval ending at time T and one starting at T shouldn't be counted as overlapping.
✅When sorting events with the same time, process ends (-1) before starts (+1). This ensures that an interval ending at T frees its resource before a new one starting at T claims it. Sort by (time, type) where end < start.
Not handling the empty input or single interval case
You access intervals[0] without checking if the array is empty. Or your loop starts at i=1 but the array has only one element.
✅Add an early return: if (intervals.length <= 1) return intervals. Or initialize your result with intervals[0] only after checking the array isn't empty. Always test with 0 and 1 intervals.
Mutating the input array during merge
You modify intervals in-place while iterating, causing skipped elements or incorrect merges. Or you modify the newInterval reference in Insert Interval and lose track of the original.
✅Build a separate result array. Never modify the input while iterating over it. For Insert Interval, it's fine to modify newInterval (you're building it up), but don't modify the original intervals array.
Interview Strategy
Interval problems are popular in FAANG interviews because they test sorting intuition, greedy reasoning, and edge case handling. Here's how to approach them.
The 5-Step Interview Flow
Clarify the Overlap Definition
'Do intervals [1,2] and [2,3] overlap? Are the endpoints inclusive or exclusive? Can intervals have zero length [5,5]?' This determines your < vs ≤ condition and is the #1 source of bugs.
Identify the Template
'I need to merge overlapping intervals → sort by start, linear merge. I need to count max overlaps → sweep line. I need to maximize non-overlapping → sort by end, greedy.' Name the technique explicitly.
State the Sort Order and Why
'I'll sort by start time because I need to process intervals chronologically and merge adjacent overlaps.' Or: 'I'll sort by end time because the greedy choice is to pick the interval that ends earliest.' Explain the reasoning.
Code the Overlap Condition Carefully
Write the overlap check as a clear condition: curr[0] <= last[1] for merge, or start >= lastEnd for greedy. Add a comment. This is where bugs hide — be explicit.
Test Edge Cases
Trace through: (1) empty input, (2) single interval, (3) all intervals overlap into one, (4) no intervals overlap, (5) one interval contained inside another [1,10] and [3,5]. State complexity: O(n log n) time, O(n) space.
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "What if intervals are streaming (not all available upfront)?" | Use a balanced BST or sorted set to maintain intervals. Insert each new interval and merge with neighbors. This gives O(log n) per insertion instead of re-sorting. |
| "What if you need to support add AND remove interval?" | Use a balanced BST (like a TreeMap) keyed by start time. Add: insert and merge with neighbors. Remove: find the interval, split if partially removed. More complex but same core logic. |
| "Can you do it without sorting?" | For merge: you could use a bucket/counting approach if the range is bounded, but sorting is the standard and expected approach. For sweep line: you need sorted events, so sorting is inherent. |
| "What if intervals are weighted (each has a value)?" | This becomes a weighted job scheduling problem. Sort by end, use DP: for each interval, either take it (value + best non-overlapping before it) or skip it. Binary search to find the last non-overlapping interval. |
| "Extend to 2D rectangles?" | Use sweep line on one axis (e.g., x) and a segment tree or balanced BST on the other axis (y). This is an advanced technique — mention it to show breadth of knowledge. |
| "What's the space complexity?" | O(n) for the result array (or O(n) for the events array in sweep line). The sort is O(log n) extra space (or O(n) depending on the sort algorithm). Mention that the input sort is in-place if allowed. |
The meta-skill
Interval problems test your ability to reduce a 2D problem (start AND end) to a 1D scan by sorting. The interviewer wants to see: (1) You immediately think "sort first." (2) You choose the right sort key (start vs end). (3) You handle the overlap condition precisely. (4) You reason about why the greedy choice is correct. Practice explaining the sort-then-scan paradigm out loud.
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
Core idea: Sort intervals to make overlaps adjacent → single-pass greedy scan. O(n log n)
Overlap condition: After sorting by start: curr.start ≤ last.end → overlap. Check < vs ≤ per problem
Merge template: Sort by start. Extend last.end = max(last.end, curr.end) on overlap. Push new interval otherwise
Sweep line: Convert to events: +1 at start, -1 at end. Sort events. Sweep with running count. Max count = answer
Greedy scheduling: Sort by END. Pick earliest-ending non-conflicting interval. Provably optimal for max non-overlapping
Insert interval: Three phases: before (end < new.start), overlapping (start ≤ new.end), after. Merge in phase 2
Intersection: Two pointers. Overlap = [max(starts), min(ends)]. Valid if lo ≤ hi. Advance earlier-ending pointer
Min removals: n - maxNonOverlapping. Sort by end, greedy count, subtract from total
Meeting rooms: Conflict check: sort by start, check consecutive. Min rooms: sweep line or min-heap of end times
Math.max trap: Always use max(last.end, curr.end) when merging — current interval might be contained inside the last one
Event ordering: Same-time events: process ends before starts. Sort by (time, type) where -1 < +1
Mental trigger: See [start, end] pairs → sort first. Merging → sort by start. Selecting → sort by end. Counting → sweep line
Decision Flowchart
What does the problem ask? ├── Merge overlapping intervals into non-overlapping? │ → Sort by START. Sweep: extend or push. (Template 1) ├── Insert a new interval into a sorted list? │ → Three phases: before, overlap (merge), after. (Template 1 variant) ├── Find intersection of two interval lists? │ → Two pointers. Overlap = [max starts, min ends]. Advance earlier end. ├── Check if any intervals overlap (conflict)? │ → Sort by start. Check if any consecutive pair overlaps. ├── Count max simultaneous overlaps / min resources? │ → Sweep line: +1 at start, -1 at end. Sort events. Max running count. │ → Or: min-heap of end times (Meeting Rooms II). ├── Maximize non-overlapping intervals? │ → Sort by END. Greedy: pick earliest-ending non-conflicting. (Template 3) ├── Minimize removals to eliminate overlaps? │ → Same as above: removals = n - maxNonOverlapping. ├── Minimum arrows / points to cover all intervals? │ → Sort by END. Greedy: place arrow at earliest end. (Template 3 variant) └── Find gaps / free time between intervals? → Merge all intervals (Template 1), then gaps = spaces between merged.
One last thing
Interval problems are one of the most predictable patterns in interviews. There are only three templates and the problems are variations of the same core idea: sort, then scan. The tricky part is choosing the right sort key and getting the overlap condition exactly right. Solve the 7 problems in Section 07, pay attention to the < vs ≤ distinction, and you'll handle any interval question with confidence.