</> MAANG.io
coding interview · 201

Intermediate

Advanced coding interview preparation covering complex algorithms, system design, and optimization techniques for senior-level positions.

0/170 solved 0% complete

Merge and Free Time

What is this?

Two people compare calendars to find a free hour. Nobody does this by checking every pair of meetings against every other — you lay both calendars out in time order and walk down them together. Sorting is what makes that walk possible, and it is the single decision that collapses interval problems from fiddly case analysis into one clean sweep.

Once busy time is merged, free time needs no algorithm at all. It is whatever is left.

flowchart TD A["sort intervals by start"] --> B["hold a current block"] B --> C{"next.start <= block.end?"} C -->|yes| D["extend: block.end = max(block.end, next.end)"] C -->|no| E["emit block, start a new one"] D --> C E --> C E --> F["free time = the gaps between emitted blocks"] G["two already-sorted lists"] --> H["two pointers, advance
whichever ends first — O(m+n), no sort"]

💡 Fun fact: max is what makes the merge handle containment without a special case. When [1, 10] is followed by [2, 3], extending with block.end = max(10, 3) leaves the block untouched — exactly right, because the second interval is entirely swallowed. A naive block.end = next.end would shrink the block to 3 and silently lose seven units of busy time, and the bug only shows up on inputs where one meeting sits inside another.

🔓 The 3 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


The one-line idea: sort by start, then either extend the current block or emit it and begin a new one. Free time is the complement of the merged blocks — and when the inputs are already sorted lists, a two-pointer walk beats sorting them together.


1. The merge sweep

intervals.sort(key=lambda x: x[0])
merged = []
for start, end in intervals:
    if merged and start <= merged[-1][1]:
        merged[-1][1] = max(merged[-1][1], end)     # extend — max handles containment
    else:
        merged.append([start, end])

Whether the comparison is <= or < is the endpoint convention, and it must be a deliberate choice: with <=, the intervals [1,3] and [3,5] merge into [1,5]; with <, they stay separate. Both are defensible, and problems specify one or the other — state your assumption before you write it.

2. Free time is the complement

Once every person's busy intervals are pooled and merged, the free stretches are simply the gaps:

merged busy:   [1,3]        [4,6]        [7,9]
gaps:                [3,4]        [6,7]

No extra logic is needed — walk the merged list and emit [previous.end, current.start] whenever there is a positive gap. The common refinement for large inputs is to merge the per-employee schedules with a heap rather than pooling and sorting everything, which is O(n log k) for k employees instead of O(n log n).

3. Two sorted lists need no sort

Meeting Scheduler gives two people's free slots, each already sorted. Sorting them together throws that away. Instead walk both with an index each:

i = j = 0
while i < len(a) and j < len(b):
    start = max(a[i][0], b[j][0])         # the overlap begins at the later start
    end   = min(a[i][1], b[j][1])         # and ends at the earlier end
    if end - start >= duration:
        return [start, start + duration]
    if a[i][1] < b[j][1]:                 # advance whichever ends first —
        i += 1                            # it cannot overlap anything later
    else:
        j += 1

The overlap of two intervals is always [max(starts), min(ends)], and advancing the one that ends first is safe because it can never intersect any later interval on the other side.


4. A 30-second worked example

intervals = [[1,3], [2,6], [8,10], [15,18]]

sorted (already):  [1,3]  [2,6]  [8,10]  [15,18]

[1,3]   → merged = [[1,3]]
[2,6]   → 2 <= 3 → extend: end = max(3, 6) = 6   → [[1,6]]
[8,10]  → 8 > 6  → emit and start new             → [[1,6], [8,10]]
[15,18] → 15 > 10 → emit and start new            → [[1,6], [8,10], [15,18]]

free time between the blocks:  [6,8]  and  [10,15]

The free stretches fell out of the merged list without a single extra comparison.


5. Where you'll actually meet this

  • Calendar and scheduling software. Free/busy lookup across several attendees is Employee Free Time exactly.
  • Resource booking. Detecting double-bookings for rooms, vehicles or equipment.
  • Genomics. Merging overlapping read alignments before computing coverage.
  • Log and trace analysis. Collapsing overlapping spans into consolidated activity periods.
  • Billing and rostering. Consolidating overlapping shifts or usage windows before invoicing.

6. Problems in this chapter

▶ Merge Intervals

Merge all overlapping intervals. Sort by start, extend with max, emit on a gap.
Pattern: sort-and-sweep merge. Target: O(n log n) time, O(n) space.

▶ Meeting Scheduler

Earliest slot of a given duration free for both people, given two sorted lists of free intervals. Two pointers, advancing whichever interval ends first.
Pattern: two-pointer intersection of sorted lists. Target: O(m + n) time, O(1) space.

▶ Employee Free Time

Time free for every employee. Pool and merge all busy intervals — or merge with a heap for large inputs — then emit the gaps.
Pattern: merge, then take the complement. Target: O(n log n), or O(n log k) with a heap.


7. Common pitfalls 🚫

  • Assigning end = next.end instead of max. Containment silently shrinks the block.
  • Not deciding the endpoint convention. Whether touching intervals merge changes the answer, and problems differ.
  • Sorting already-sorted lists together. Meeting Scheduler is O(m + n); sorting makes it O((m+n) log(m+n)) for nothing.
  • Advancing the wrong pointer. Advance the interval that ends first — the other may still overlap something later.
  • Emitting zero-length gaps. Adjacent merged blocks produce an empty free interval that should be filtered out.
  • Forgetting empty input. No intervals means no merged blocks and no free time, not a crash.
  • Sorting by end time. It works for "maximum non-overlapping intervals" but not for merging — different problem, different key.

8. Key takeaways

  1. Sort by start. It is the decision that removes most of the case analysis.
  2. Extend with max, which handles containment for free.
  3. Free time is the complement of merged busy time — no second algorithm.
  4. Already sorted? Use two pointers. Advance whichever interval ends first.
  5. Overlap is [max(starts), min(ends)] — worth memorising as a formula.
  6. State the endpoint convention before writing the comparison.
  7. Why interviewers like it: everyone understands calendars, so the problem statement takes ten seconds and the remaining time is entirely about how carefully you handle the boundaries.

Order: Merge Intervals → Meeting Scheduler → Employee Free Time.

Meeting Scheduler

Core idea: Sort each person's free slots by start time, then walk two pointers — one per list — in lockstep. At every step the current slots overlap on [max(starts), min(ends)]; if that window is long enough, you're done. If not, the slot that ends first can never help a later, even-later overlap, so throw it away and advance only that pointer. The slots are random, so sorting is what turns "compare everything to everything" into a single linear sweep.


Problem, rephrased

Two engineers in different time zones both want to pair on a bug, and you're writing the scheduler. Each one hands you their free blocks for the day — a list of [start, end] windows during which they're available — and the pairing session needs duration uninterrupted minutes. Your job: return the earliest duration-long window that falls inside a free block of both people. If there's no such window, return an empty array so the UI can say "no common time."

Formally (LeetCode 1229): given the availability arrays slots1 and slots2 of two people and a meeting duration, return the earliest time slot [start, start + duration] that works for both and is exactly duration long. A slot is an inclusive range [start, end]. It's guaranteed that no two slots of the same person intersect — each person's own blocks are already disjoint. If nothing fits, return [].

![Setup: slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration 8; [60,120] and [60,70] overlap on [60,70] (length 10 >= 8), so answer = [60, 68]](https://maangioassets.blob.core.windows.net/diagrams/coding-interview/201/chapters/intervals/merge-and-free-time/meeting-scheduler-diagram-1.svg)

Inputs / outputs

slots1 slots2 duration output notes
[[10,50],[60,120],[140,210]] [[0,15],[60,70]] 8 [60, 68] overlap [60,70] is long enough
[[10,50],[60,120],[140,210]] [[0,15],[60,70]] 12 [] longest common window is only 10
[[0,100]] [[40,60]] 20 [40, 60] exact fit — overlap length == duration
[[0,10]] [[20,30]] 5 [] the two never overlap at all

You return a single [start, start + duration] pair — the earliest one — or [].


Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.