Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
The input is already sorted and non-overlapping, so you don't need to re-sort. Walk the list once in three phases: copy every interval that ends before the new one starts, merge every interval that overlaps the new one into a single expanding interval, then copy the rest. That's O(n) time, O(n) output.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you notice the input is already sorted and exploit it for O(n), instead of blindly appending the new interval and re-running a full sort-and-merge.
Where candidates slip
Re-sorting the array (paying O(n log n) for nothing), or getting the overlap condition wrong so touching intervals like [1,2] and [2,3] don't merge.
How to stand out
State the three phases before coding, name the overlap test precisely (start <= end on both sides), and mention you could binary-search the insertion point to cut the 'before' scan to O(log n).
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewAdd an interval to a list of intervalsIs the input already sorted & disjoint?NoYesExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Append, Sort, Merge — Reuse Merge Intervals
1Hint 1Explore hintHide hint
You already know how to merge a list of unsorted intervals. Can you turn this problem into that one?
2Hint 2Explore hintHide hint
Push newInterval onto the list, sort everything by start, then do the standard sweep-merge.
3Hint 3Explore hintHide hint
It works and it's easy to reason about — but ask yourself what the 'already sorted' guarantee was for.
A · Core idea
Treat the problem as Merge Intervals with one extra element. Append newInterval to the list, sort the whole thing by start, then sweep left-to-right fusing any interval whose start is ≤ the previous interval's end.
Why reach for this first. If you've already solved Merge Intervals, this is the lowest-risk path: you're reusing a routine you trust instead of writing new index gymnastics. In an interview it's a perfectly good thing to say — 'the safe version is to append and re-merge' — because it shows you can decompose a new problem into a known one.
Why it's not the answer they want. The prompt hands you a list that is already sorted and already non-overlapping. Sorting it again throws that guarantee in the bin and pays O(n log n) for information you were given for free. The interviewer chose 'sorted input' on purpose — it's a flashing sign that an O(n) solution exists. Use this as the warm-up, then beat it.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
intervals = [[1,3],[6,9]], newInterval = [2,5]append
add [2,5]
[[1,3],[6,9],[2,5]][[1,5],[6,9]]C · Code with teaching comments
Reference implementation previewpublic int[][] insert(int[][] intervals, int[] newInterval) { List<int[]> all = new ArrayList<>(Arrays.asList(intervals)); all.add(newInterval); all.sort((a, b) -> Integer.compare(a[0], b[0])); List<int[]> merged = new ArrayList<>();Explore the full codeHide the full code
D · Why it works and complexityO(n log n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Reducing a new problem to a solved one is a real skill — but only when you can't do better. Here the sorted-input guarantee is the tell that a linear solution is reachable, so this O(n log n) version is a stepping stone, not the destination.
ComplexityO(n log n) time, O(n) space — The sort dominates at O(n log n); the merge sweep is O(n). The sort is pure waste here because the input minus the new interval was already ordered.
E · Edge cases and pitfalls5intervals = [], newInterval = [5,7] — all_iv = [[5,7]]; sort is a no-op; merge appends it → returns [[5,7]].Explore edge casesHide edge cases
Edge cases handled
intervals = [], newInterval = [5,7]all_iv = [[5,7]]; sort is a no-op; merge appends it → returns [[5,7]].intervals = [[1,5]], newInterval = [2,3]newInterval is fully contained; merge keeps [1,5] since max(5,3)=5 → [[1,5]].
Pitfalls
- Mutating the input `intervals` array in place while sorting — copy first.
- Using `<=` instead of `<` in the disjoint test merges touching intervals (here that's desired, but be deliberate).
- Forgetting this re-sort is the whole inefficiency — don't present it as the final answer.
Approach 2 of 2 · Optimal
Three-Phase Linear Sweep (Optimal)
1Hint 1Explore hintHide hint
The list is already sorted and disjoint. What can you copy verbatim before you even look at the new interval?
2Hint 2Explore hintHide hint
Split the work into three contiguous regions: intervals entirely before the new one, intervals that overlap it, and intervals entirely after.
3Hint 3Explore hintHide hint
In the overlap region, keep expanding newInterval: newStart = min(newStart, cur.start), newEnd = max(newEnd, cur.end). Append it once when overlapping stops, then copy the rest.
A · Core idea
Walk the list with a single index i, in three sequential phases.
Phase 1 — before. While the current interval ends strictly before the new interval starts (intervals[i][1] < newStart), it can't possibly overlap, so copy it to the result untouched and advance. Because the input is sorted, once this stops, every remaining interval starts at or after this point.
Phase 2 — overlap. While the current interval starts at or before the new interval ends (intervals[i][0] <= newEnd), it overlaps the (possibly already-grown) new interval. Don't append these — instead absorb them: newStart = min(newStart, intervals[i][0]) and newEnd = max(newEnd, intervals[i][1]). The new interval swells to cover the whole overlapping run. When the loop ends, append the merged new interval exactly once.
Phase 3 — after. Everything left starts strictly after the merged interval ends, so copy the remaining intervals verbatim.
Why three loops beat one branchy loop. You could do this in a single loop with if before / elif overlap / else after, but the three-phase version makes the invariant obvious: each phase owns a contiguous slice of the sorted input, and the index only moves forward. It's easier to reason about and easier to get right under interview pressure.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]Array state
before
[1,2] ends at 2 < 4 → copy
[[1,2],[3,10],[12,16]]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The sorted, disjoint guarantee means the answer is naturally partitioned into three contiguous blocks: untouched-left, one fused middle, untouched-right. The only 'work' is fusing the middle — and fusing is just running min over starts and max over ends. Spot that partition and the code writes itself. If you wanted to shave the 'before' phase to O(log n), binary-search for the first interval whose end ≥ newStart — but the copy still makes the whole thing O(n), so it rarely matters.
ComplexityO(n) time, O(n) space — Each interval is visited by exactly one of the three loops, doing O(1) work — so O(n) time. Output holds up to n+1 intervals → O(n) space.
E · Edge cases and pitfalls5intervals = [[1,3],[6,9]], newInterval = [2,5] — Before-phase copies nothing (1's end 3 ≥ 2); overlap absorbs [1,3] → [1,5]; [6,9] copied after → [[1,5],[6,9]].Explore edge casesHide edge cases
Edge cases handled
intervals = [[1,3],[6,9]], newInterval = [2,5]Before-phase copies nothing (1's end 3 ≥ 2); overlap absorbs [1,3] → [1,5]; [6,9] copied after → [[1,5],[6,9]].intervals = [[1,5]], newInterval = [6,8]Before-phase copies [1,5] (5 < 6); overlap runs zero times; append [6,8] → [[1,5],[6,8]] (a gap-filling insert).
Pitfalls
- Using `<=` in phase 1's test makes a touching interval skip the merge — phase 1 must be strict `<`.
- Appending the new interval inside the overlap loop produces duplicates — append once after the loop.
- Forgetting phase 3 drops every interval after the merge region.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int[][] insert(int[][] intervals, int[] newInterval) { List<int[]> res = new ArrayList<>(); int i = 0, n = intervals.length; int newStart = newInterval[0], newEnd = newInterval[1]; while (i < n && intervals[i][1] < newStart) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Re-sorting the input you were handed sorted
The prompt guarantees the list is already sorted by start. Sorting again costs O(n log n) and signals you missed the entire reason the input was specified that way.
Explore the code comparisonHide the code comparison
Re-sorting the input you were handed sorted
The prompt guarantees the list is already sorted by start. Sorting again costs O(n log n) and signals you missed the entire reason the input was specified that way.
Don't write this
all_iv = intervals + [newInterval]
all_iv.sort(key=lambda iv: iv[0]) # O(n log n) — wastedWrite this instead
# No sort. Walk once in three phases — O(n).
while i < n and intervals[i][1] < new_start:
res.append(intervals[i]); i += 12Appending the new interval inside the overlap loop
Each overlapping interval would push a copy of the (still-growing) new interval, producing duplicates. The merged interval is finalized only after the overlap run ends.
Explore the code comparisonHide the code comparison
Appending the new interval inside the overlap loop
Each overlapping interval would push a copy of the (still-growing) new interval, producing duplicates. The merged interval is finalized only after the overlap run ends.
Don't write this
while i < n and intervals[i][0] <= new_end:
new_end = max(new_end, intervals[i][1])
res.append([new_start, new_end]) # appends every iteration
i += 1Write this instead
while i < n and intervals[i][0] <= new_end:
new_start = min(new_start, intervals[i][0])
new_end = max(new_end, intervals[i][1])
i += 1
res.append([new_start, new_end]) # once3Wrong inequality in the before-phase
Phase 1 must stop on the first interval that could touch the new one. Using `<=` lets a touching interval (end == newStart) slip into the copy-as-is phase and never merge.
Explore the code comparisonHide the code comparison
Wrong inequality in the before-phase
Phase 1 must stop on the first interval that could touch the new one. Using `<=` lets a touching interval (end == newStart) slip into the copy-as-is phase and never merge.
Don't write this
while (i < n && intervals[i][1] <= newStart) { // skips a toucher
res.add(intervals[i]); i++;
}Write this instead
while (i < n && intervals[i][1] < newStart) { // strict
res.add(intervals[i]); i++;
}Revise
Carry the pattern into the interview
Pattern to remember
Three-phase linear sweep over sorted, non-overlapping intervals
Code pattern to memorize
# 1) before: while end_i < newStart: keep interval
# 2) overlap: while start_i <= newEnd: new = [min(starts), max(ends)]
# 3) after: append merged new, then keep the restRules to remember
- 1The input is already sorted by start and non-overlapping — re-sorting is the rookie move that throws away the O(n) you were handed.
- 2Two intervals overlap iff
a.start <= b.end AND b.start <= a.end. Touching endpoints ([1,2]and[2,3]) count as overlapping for this problem. - 3While merging, grow the new interval in place:
newStart = min(newStart, cur.start),newEnd = max(newEnd, cur.end). - 4Append the merged interval once, after the overlap phase ends — not inside the loop.
- 5Three sequential
whileloops over the same indexiis cleaner than one loop with branchyif/else— say that out loud.
Use this pattern when
- Input intervals are already sorted and disjoint, and you insert/merge one new range
- You want O(n) without paying for a sort
- Calendar / booking 'add this slot and merge conflicts' framing
- A single new interval, not a batch — batch turns it into Merge Intervals
Choose another pattern when
- Intervals are unsorted → sort first, then it's Merge Intervals (LC 56)
- You must insert many intervals → re-merge the whole set instead
- You only need to know IF it overlaps, not the merged result → a single boolean scan
- Endpoints are exclusive (`[1,2)` doesn't touch `[2,3)`) → flip the comparison to strict `<`
Interview takeaway
When an interval list arrives pre-sorted, the sort is a gift — spend it on a single linear pass. Insert Interval is the cleanest example of the three-phase 'before / overlap / after' sweep that also powers Merge Intervals and most calendar problems.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternThree-phase interval sweep on sorted input. Recognise it whenever you insert/merge into an already-ordered, disjoint interval list. Same 'before / overlap / after' partition powers Merge Intervals and most calendar-booking problems.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Merge IntervalsUnsorted input — sort by start, then the same sweep-merge.
- Non-overlapping IntervalsGreedy: sort by end, count keepers, removals = n − keepers.
- Meeting RoomsSort by start; if any meeting starts before the previous ends, you can't attend all.
- Meeting Rooms IIMax concurrent overlaps via a min-heap of end times.