Skip to content

57. Insert Interval (Medium)

Problem

Given intervals sorted by start (non-overlapping) and a new newInterval, insert it into intervals such that the result is still non-overlapping (merge as necessary).

Example

  • intervals = [[1,3],[6,9]], newInterval = [2,5][[1,5],[6,9]]
  • intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8][[1,2],[3,10],[12,16]]

LeetCode 57 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, append and merge

Add to list, sort, run Merge Intervals.

def insert(intervals, new_interval):
intervals = [list(iv) for iv in intervals] + [list(new_interval)]
intervals.sort(key=lambda x: x[0]) # L1: O(n log n)
result = []
for iv in intervals: # L2: O(n) sweep
if result and iv[0] <= result[-1][1]:
result[-1][1] = max(result[-1][1], iv[1])
else:
result.append(list(iv))
return result

The sort is the load-bearing step. After sort, the linear merge is the same template as LeetCode 56.

Complexity

  • Time: O(nlogn)O(n log n).
  • Space: O(n)O(n).

Approach 2: Binary search for insertion point + left-merge + right-merge

Find where newInterval goes; then walk left and right, merging overlaps.

import bisect
def insert(intervals, new_interval):
intervals = [list(iv) for iv in intervals]
starts = [iv[0] for iv in intervals]
idx = bisect.bisect_left(starts, new_interval[0]) # L1: O(log n) search
intervals.insert(idx, list(new_interval)) # L2: O(n) shift
# Merge leftward while previous overlaps
while idx > 0 and intervals[idx - 1][1] >= intervals[idx][0]:
intervals[idx - 1][1] = max(intervals[idx - 1][1], intervals[idx][1])
intervals.pop(idx)
idx -= 1
# Merge rightward while next overlaps
while idx + 1 < len(intervals) and intervals[idx][1] >= intervals[idx + 1][0]:
intervals[idx][1] = max(intervals[idx][1], intervals[idx + 1][1])
intervals.pop(idx + 1)
return intervals

The binary search saves work on the search, but the insert and pop calls are O(n)O(n) shifts, so the worst case is the same as a single linear pass.

Same O(n)O(n) in the worst case.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 3: Single linear pass (canonical, O(n)O(n))

Three phases over the sorted list:

  1. Copy all intervals strictly before newInterval.
  2. Merge any overlapping with newInterval.
  3. Copy the rest.
def insert(intervals, new_interval):
result = [] # L1: O(1) setup
i = 0 # L2: O(1)
n = len(intervals) # L3: O(1)
# 1. before
while i < n and intervals[i][1] < new_interval[0]: # L4: O(1) per check
result.append(intervals[i]) # L5: O(1) per append
i += 1
# 2. overlap
while i < n and intervals[i][0] <= new_interval[1]: # L6: O(1) per check
new_interval = [min(new_interval[0], intervals[i][0]),
max(new_interval[1], intervals[i][1])] # L7: O(1) per merge
i += 1
result.append(new_interval) # L8: O(1)
# 3. after
while i < n: # L9: O(1) per check
result.append(intervals[i]) # L10: O(1) per append
i += 1
return result

Where the time goes, line by line

Variables: n = len(intervals) (the existing intervals; newInterval is a single interval).

LinePer-call costTimes executedContribution
L4, L5 (phase 1 copy)O(1)O(1) eachup to nO(n)O(n)
L6, L7 (phase 2 merge)O(1)O(1) eachup to nO(n)O(n)
L8 (insert merged)O(1)O(1)1O(1)O(1)
L9, L10 (phase 3 copy)O(1)O(1) eachup to nO(n)O(n) ← all phases together

No phase dominates; every interval is visited exactly once across the three phases. Total work is O(n)O(n) with no sorting because the input is already sorted.

Complexity

  • Time: O(n)O(n), driven by the single pass across all three phases (L4-L10).
  • Space: O(n)O(n).

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

ApproachTimeSpace
Append + re-mergeO(nlogn)O(n log n)O(n)O(n)
Binary search + local mergeO(n)O(n)O(n)O(n)
Single-pass three-phaseO(n)O(n)O(n)O(n)

The three-phase template is the cleanest answer.

Test cases

# Quick smoke tests, paste into a REPL or save as test_057_insert_interval.py and run.
# Uses the canonical implementation (Approach 3).
def insert(intervals, new_interval):
result = []
i = 0
n = len(intervals)
while i < n and intervals[i][1] < new_interval[0]:
result.append(intervals[i])
i += 1
while i < n and intervals[i][0] <= new_interval[1]:
new_interval = [min(new_interval[0], intervals[i][0]),
max(new_interval[1], intervals[i][1])]
i += 1
result.append(new_interval)
while i < n:
result.append(intervals[i])
i += 1
return result
def _run_tests():
# Example 1 from problem statement
assert insert([[1,3],[6,9]], [2,5]) == [[1,5],[6,9]]
# Example 2: spans multiple intervals
assert insert([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8]) == [[1,2],[3,10],[12,16]]
# Insert at the start (no overlap)
assert insert([[3,5],[6,9]], [1,2]) == [[1,2],[3,5],[6,9]]
# Insert at the end (no overlap)
assert insert([[1,2],[3,5]], [7,9]) == [[1,2],[3,5],[7,9]]
# Completely subsumes all existing intervals
assert insert([[1,2],[3,4],[5,6]], [0,10]) == [[0,10]]
# Empty intervals list
assert insert([], [1,5]) == [[1,5]]
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Arrays, sorted list of intervals; in-place walk
  • Intervals, range-boundary tactics for overlap, containment, scheduling, and sweep-line problems.
  • Merge Intervals, sorted-boundary tactics for combining overlapping ranges and maintaining the current covered span.