Insert Interval
Difficulty: Medium Source: NeetCode
Problem
You are given an array of non-overlapping intervals
intervalswhereintervals[i] = [start_i, end_i]represent the start and end of the i-th interval andintervalsis sorted in ascending order bystart_i. You are also given an intervalnewInterval = [start, end]that represents the start and end of another interval.Insert
newIntervalintointervalsso thatintervalsis still sorted in ascending order by start_i andintervalsstill does not have any overlapping intervals (merge overlapping intervals if necessary).Return the resulting array of intervals.
Example 1: Input:
intervals = [[1,3],[6,9]],newInterval = [2,5]Output:[[1,5],[6,9]]Example 2: Input:
intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]],newInterval = [4,8]Output:[[1,2],[3,10],[12,16]]Constraints:
0 <= intervals.length <= 10^4intervals[i].length == 20 <= start_i <= end_i <= 10^5intervalsis sorted bystart_iin ascending ordernewInterval.length == 20 <= start <= end <= 10^5
Prerequisites
Before attempting this problem, you should be comfortable with:
- Intervals — understanding how intervals overlap (two intervals [a,b] and [c,d] overlap if a <= d and c <= b)
- Greedy — making locally optimal choices (extend the merged interval as far right as possible)
- Arrays — iterating and building a result list
1. Brute Force
Intuition
Insert newInterval into the list, then re-sort by start time, then run the standard merge-intervals algorithm. It is simple and correct, but the sort costs extra time that we do not actually need since the input is already sorted.
Algorithm
- Append
newIntervaltointervals. - Sort
intervalsby start time. - Initialize
resultwith the first interval. - For each remaining interval, if it overlaps the last entry in
result, extend that entry’s end. Otherwise append the interval toresult. - Return
result.
Solution
def insert_brute(intervals, newInterval):
intervals.append(newInterval)
intervals.sort(key=lambda x: x[0])
result = [intervals[0]]
for start, end in intervals[1:]:
last_end = result[-1][1]
if start <= last_end:
result[-1][1] = max(last_end, end)
else:
result.append([start, end])
return result
print(insert_brute([[1,3],[6,9]], [2,5])) # [[1,5],[6,9]]
print(insert_brute([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8])) # [[1,2],[3,10],[12,16]]
print(insert_brute([], [5,7])) # [[5,7]]
Complexity
- Time:
O(n log n)— dominated by the sort - Space:
O(n)— result list
2. Three-Phase Linear Scan
Intuition
Because the input is already sorted by start time, we can handle this in one pass without sorting. Think of it as three distinct phases: first, collect all intervals that end before the new interval starts (they can never overlap); second, merge all intervals that overlap with the new interval by extending it; third, collect everything that starts after the merged interval ends. This runs in O(n) time.
Algorithm
- Initialize
result = []andi = 0. - Phase 1 — Add intervals before new one: While
i < len(intervals)andintervals[i][1] < newInterval[0], appendintervals[i]toresultand advancei. - Phase 2 — Merge overlapping intervals: While
i < len(intervals)andintervals[i][0] <= newInterval[1], expandnewIntervalto cover the union:newInterval[0] = min(newInterval[0], intervals[i][0]),newInterval[1] = max(newInterval[1], intervals[i][1]), advancei. Then append the mergednewInterval. - Phase 3 — Add remaining intervals: Append
intervals[i:]toresult. - Return
result.
Solution
def insert(intervals, newInterval):
result = []
i = 0
n = len(intervals)
# Phase 1: intervals that end before newInterval starts — no overlap possible
while i < n and intervals[i][1] < newInterval[0]:
result.append(intervals[i])
i += 1
# Phase 2: merge all overlapping intervals into newInterval
while i < n and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(newInterval[0], intervals[i][0])
newInterval[1] = max(newInterval[1], intervals[i][1])
i += 1
result.append(newInterval)
# Phase 3: intervals that start after newInterval ends — no overlap possible
while i < n:
result.append(intervals[i])
i += 1
return result
print(insert([[1,3],[6,9]], [2,5])) # [[1,5],[6,9]]
print(insert([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8])) # [[1,2],[3,10],[12,16]]
print(insert([], [5,7])) # [[5,7]]
print(insert([[1,5]], [2,3])) # [[1,5]]
print(insert([[1,5]], [6,8])) # [[1,5],[6,8]]
Complexity
- Time:
O(n)— single pass through the list - Space:
O(n)— result list
Common Pitfalls
Forgetting the edge case where the list is empty. When intervals = [], phases 1 and 3 are both skipped, and phase 2 just appends newInterval immediately — the code handles this naturally without a special case.
Off-by-one on overlap conditions. An interval [1,5] and newInterval = [5,7] DO overlap (they share the point 5). The condition for “no overlap before” is intervals[i][1] < newInterval[0] (strictly less), and for “overlapping” it is intervals[i][0] <= newInterval[1] (less than or equal).
Mutating the input. The merging step modifies newInterval in place. If the caller still needs the original newInterval, pass a copy instead.