0Pricing
DSA Interview Prep · Lesson

Interval Scheduling and Merging

Solve meeting-rooms and non-overlapping-intervals by sorting on end time, and merge-intervals by sorting on start time.

Interval Scheduling and Merging is a free DSA Interview Prep lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DSA Interview Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Interval Problems Overview

Interval problems appear constantly in scheduling, calendar management, and resource allocation interviews. The key patterns are: merge overlapping intervals, count minimum meeting rooms, find maximum non-overlapping set, and insert a new interval. Most interval problems start with the same step: sort intervals by start time (or end time, depending on the problem). Getting the sorting key right is often the hardest part.

# Intervals: each = [start, end] (inclusive or exclusive by problem)
# Example:
intervals = [[1,3],[2,6],[8,10],[15,18]]
# Sorted by start (already sorted here)
# Visually:
# [1,3]    |-|
# [2,6]      |---|
# [8,10]             |--|
# [15,18]                    |---|
print('Intervals ready for analysis')

Merge Overlapping Intervals

Merge Intervals (LeetCode 56): given a list of intervals, merge all overlapping ones. Algorithm: sort by start time. Walk through the sorted list; if the current interval overlaps with the last merged interval (its start ≤ last merged end), extend the last merged interval's end to the max of both ends. Otherwise, append the current interval as a new merged interval. Time: O(n log n) for sorting, O(n) for merging.

def merge_intervals(intervals):
    intervals.sort(key=lambda x: x[0])  # sort by start
    merged = [intervals[0]]
    for start, end in intervals[1:]:
        last_end = merged[-1][1]
        if start <= last_end:
            # Overlapping: extend the last interval
            merged[-1][1] = max(last_end, end)
        else:
            # Non-overlapping: add as new interval
            merged.append([start, end])
    return merged

print(merge_intervals([[1,3],[2,6],[8,10],[15,18]]))
# [[1,6],[8,10],[15,18]]
print(merge_intervals([[1,4],[4,5]]))
# [[1,5]] (touching intervals merge)

Insert Interval

Insert Interval (LeetCode 57): given a sorted non-overlapping list, insert a new interval and re-merge. Walk through in three phases: (1) Add all intervals that end before the new interval starts. (2) Merge all intervals that overlap with the new interval (expand its boundaries). (3) Add all remaining intervals. This is a single O(n) pass after the O(n log n) sort (which is already done in this problem).

def insert_interval(intervals, new_interval):
    result = []
    i = 0
    n = len(intervals)
    # Phase 1: intervals before new_interval
    while i < n and intervals[i][1] < new_interval[0]:
        result.append(intervals[i])
        i += 1
    # Phase 2: merge overlapping intervals
    while i < n and intervals[i][0] <= new_interval[1]:
        new_interval[0] = min(new_interval[0], intervals[i][0])
        new_interval[1] = max(new_interval[1], intervals[i][1])
        i += 1
    result.append(new_interval)
    # Phase 3: remaining intervals
    while i < n:
        result.append(intervals[i])
        i += 1
    return result

print(insert_interval([[1,3],[6,9]], [2,5]))  # [[1,5],[6,9]]
print(insert_interval([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8]))
# [[1,2],[3,10],[12,16]]

Meeting Rooms I: Can Attend All?

Meeting Rooms I (LeetCode 252): given meeting time intervals, determine if a person can attend all meetings. Sort by start time; if any meeting starts before the previous one ends, they overlap. This is the simplest interval check — O(n log n) total. The key insight: after sorting, you only need to compare consecutive pairs.

def can_attend_meetings(intervals):
    intervals.sort(key=lambda x: x[0])
    for i in range(1, len(intervals)):
        # Current meeting starts before previous ends?
        if intervals[i][0] < intervals[i-1][1]:
            return False
    return True

print(can_attend_meetings([[0,30],[5,10],[15,20]]))  # False (0,30 overlaps 5,10)
print(can_attend_meetings([[7,10],[2,4]]))           # True (4 < 7, no overlap)

Meeting Rooms II: Minimum Rooms

Meeting Rooms II (LeetCode 253): find the minimum number of conference rooms required to hold all meetings simultaneously. Use a min-heap to track the earliest-ending room. Sort meetings by start time. For each new meeting: if it starts after the earliest-ending room's end time, reuse that room (pop and push). Otherwise, open a new room. The heap size at the end equals the rooms needed.

import heapq

def min_meeting_rooms(intervals):
    if not intervals: return 0
    intervals.sort(key=lambda x: x[0])  # sort by start
    heap = []  # min-heap of end times
    for start, end in intervals:
        if heap and heap[0] <= start:
            heapq.heapreplace(heap, end)  # reuse earliest-ending room
        else:
            heapq.heappush(heap, end)     # open a new room
    return len(heap)

print(min_meeting_rooms([[0,30],[5,10],[15,20]]))  # 2
print(min_meeting_rooms([[7,10],[2,4]]))           # 1
print(min_meeting_rooms([[9,10],[4,9],[4,17]]))    # 2

Sweep Line Alternative for Room Count

An alternative O(n log n) approach: sweep line. Create events for each interval start (+1) and end (-1). Sort all events by time (ties: end before start if you want non-inclusive). Sweep left to right, maintaining a running count of active meetings. The maximum count is the minimum rooms needed. This is more intuitive for some and generalises to other counting problems on intervals.

def min_rooms_sweep(intervals):
    events = []
    for start, end in intervals:
        events.append((start, 1))   # meeting starts
        events.append((end, -1))    # meeting ends
    # Sort: same time → end (-1) before start (1) if exclusive
    events.sort(key=lambda x: (x[0], x[1]))
    max_rooms = current = 0
    for _, delta in events:
        current += delta
        max_rooms = max(max_rooms, current)
    return max_rooms

print(min_rooms_sweep([[0,30],[5,10],[15,20]]))  # 2
print(min_rooms_sweep([[1,5],[2,6],[3,7]]))       # 3 (all overlap at t=3)

Non-Overlapping Intervals: Maximum Selection

Non-Overlapping Intervals (LeetCode 435): find the minimum number of intervals to remove to make the rest non-overlapping. This is equivalent to finding the maximum number of non-overlapping intervals (activity selection) and returning the rest as removals. Sort by end time: greedily keep the interval that ends earliest (maximises room for future intervals). When the next interval overlaps, discard it (count a removal).

def erase_overlap_intervals(intervals):
    if not intervals: return 0
    intervals.sort(key=lambda x: x[1])  # sort by END time
    removals = 0
    last_end = float('-inf')
    for start, end in intervals:
        if start >= last_end:
            last_end = end  # keep this interval
        else:
            removals += 1   # remove this interval (it overlaps)
    return removals

print(erase_overlap_intervals([[1,2],[2,3],[3,4],[1,3]]))  # 1 (remove [1,3])
print(erase_overlap_intervals([[1,2],[1,2],[1,2]]))        # 2
print(erase_overlap_intervals([[1,2],[2,3]]))              # 0 (no overlap)

Why Sort by End Time, Not Start?

For activity selection (maximum non-overlapping set), sorting by end time is provably optimal. Intuition: an activity that ends early leaves more room for future activities. If we sort by start time, we might pick a very long early-starting activity that blocks many shorter later activities. Exchange argument: if optimal picks activity A over the earliest-ending G, swap A for G — G doesn't end later, so it conflicts with nothing A didn't conflict with.

# Counterexample for sorting by START time:
# [[1,10],[2,3],[4,5]] — sorted by start: [1,10],[2,3],[4,5]
# Sort-by-start greedy keeps [1,10], can't add [2,3] or [4,5] (all overlap [1,10])
# Selects: 1 interval

# Sort-by-end greedy:
# [[2,3],[4,5],[1,10]] — sorted by end
# Keep [2,3] (end=3), then [4,5] (start=4 >= 3, keep), then [1,10] (start=1 < 5, skip)
# Selects: 2 intervals — OPTIMAL

intervals = [[1,10],[2,3],[4,5]]
intervals.sort(key=lambda x: x[1])
last_end = float('-inf')
count = 0
for s, e in intervals:
    if s >= last_end:
        count += 1; last_end = e
print('Max non-overlapping:', count)  # 2

Interval List Intersections

Interval List Intersections (LeetCode 986): find all intersecting pairs from two sorted interval lists. Use a two-pointer approach. At each step, compute the intersection of the current pair (max of starts, min of ends). If start ≤ end, the intersection is valid. Then advance the pointer of whichever interval ends first. O(m+n) time.

def interval_intersection(A, B):
    result = []
    i = j = 0
    while i < len(A) and j < len(B):
        # Intersection boundaries
        lo = max(A[i][0], B[j][0])
        hi = min(A[i][1], B[j][1])
        if lo <= hi:
            result.append([lo, hi])  # valid intersection
        # Advance pointer of interval that ends first
        if A[i][1] < B[j][1]:
            i += 1
        else:
            j += 1
    return result

A = [[0,2],[5,10],[13,23],[24,25]]
B = [[1,5],[8,12],[15,24],[25,26]]
print(interval_intersection(A, B))
# [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]

Partition Labels

Partition Labels (LeetCode 763): partition a string into as many parts as possible such that each character appears in at most one part. Greedy: for each character, find its last occurrence. Walk the string maintaining a max_end. When i == max_end, the current partition is complete — record its length and start a new partition. This is an interval-merging problem in disguise.

def partition_labels(s):
    last = {c: i for i, c in enumerate(s)}  # last occurrence of each char
    partitions = []
    start = max_end = 0
    for i, c in enumerate(s):
        max_end = max(max_end, last[c])
        if i == max_end:  # partition complete
            partitions.append(max_end - start + 1)
            start = i + 1
    return partitions

print(partition_labels('ababcbacadefegdehijhklij'))
# [9, 7, 8] — parts 'ababcbaca', 'defegde', 'hijhklij'

Interval Problems Summary

Master these four patterns for intervals: (1) Merge: sort by start, extend last if overlap. (2) Count rooms: sort by start, min-heap of end times. (3) Max non-overlapping: sort by end, greedily select. (4) Insert: three-phase linear scan. Sort key matters: merge uses start, max-selection uses end. Time complexity is always O(n log n) dominated by sort; merge/scan are O(n).

# Quick reference:
# Merge intervals:         sort by start, extend if overlap
# Insert interval:         three-phase linear scan
# Meeting rooms (can?):   sort by start, check consecutive overlap
# Meeting rooms (min?):   sort by start, min-heap of end times / sweep
# Max non-overlapping:    sort by END, greedy keep
# Min removals:           n - max_non_overlapping
# Interval intersection:  two pointers on sorted lists

print('Pattern: sort key is the decisive choice')
print('Merge → sort by start')
print('Activity selection → sort by end')
print('Room count → sort by start + heap of ends')

Quick Check

Test your understanding of Data Structures & Algorithms — Coding Interview Prep concepts from this lesson.

Lesson Recap

In this lesson you learned: merge intervals by sorting by start and extending the last interval if overlap occurs, minimum meeting rooms uses sort-by-start plus a min-heap of end times, reusing rooms when the earliest-ending room is free, and maximum non-overlapping intervals uses sort-by-end-time greedy selection. Next up we tackle Jump Game I and II — reachability and minimum-jumps problems solved with greedy range expansion.

Frequently asked questions

Is the “Interval Scheduling and Merging” lesson free?

Yes — the full text of “Interval Scheduling and Merging” is free to read here on the web, and the DSA Interview Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DSA Interview Prep course, upgrade to CoddyKit PRO.

What will I learn in “Interval Scheduling and Merging”?

Solve meeting-rooms and non-overlapping-intervals by sorting on end time, and merge-intervals by sorting on start time. You practise DSA Interview Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start DSA Interview Prep?

No prior experience is required. DSA Interview Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Interval Scheduling and Merging” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this DSA Interview Prep lesson?

Yes. Every DSA Interview Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Greedy vs DP: When to Use Each
  2. Interval Scheduling and Merging
  3. Jump Game I and II
  4. Task Scheduler and Gas Station
← Back to DSA Interview Prep