FDEInterviews logo
Coding & DSA / 04
easyPalantirMeta

Insert a new interval into a sorted, non-overlapping interval list

The follow-up interviewers reach for when merge-intervals goes too smoothly. The three-phase scan is elegant, but only if you've internalized the overlap condition most candidates have to re-derive under pressure.

Updated Aug 2026 · Grounded in real Forward Deployed Engineer interview loops and written to a senior-engineer editorial bar.

TL;DR: Single O(n) pass in three phases: copy intervals strictly before the new one, absorb every interval that overlaps it (widening start/end), then copy the rest. Two intervals overlap iff a <= d and c <= b.

How to approach it This is usually asked as a follow-up to merge-intervals, and the lazy answer ("append, then re-run merge") works but wastes the sorted invariant. State both options and their costs: re-merge is O(n log n); exploiting sortedness is O(n) in a single pass. Then name the structure out loud: the result has three phases: intervals entirely before the new one, intervals that overlap it (absorb them), intervals entirely after.

The overlap condition is worth writing on the whiteboard before coding: intervals [a, b] and [c, d] overlap iff a <= d and c <= b. Deriving it calmly is a known differentiator on this question.

A strong answer

def insert_interval(intervals: list[list[int]],
                    new: list[int]) -> list[list[int]]:
    start, end = new
    result, i, n = [], 0, len(intervals)

    while i < n and intervals[i][1] < start:      # phase 1: strictly before
        result.append(intervals[i]); i += 1

    while i < n and intervals[i][0] <= end:       # phase 2: overlapping, absorb
        start = min(start, intervals[i][0])
        end = max(end, intervals[i][1])
        i += 1
    result.append([start, end])

    result.extend(intervals[i:])                  # phase 3: strictly after
    return result

Each loop guard is one half of the overlap condition. Say that, because it shows the code follows from the math rather than trial and error. O(n) time, one pass, no sort.

The absorbing-three case from the tests below, traced by running the code, shows the phase the editor note warns about:

StepInterval seenGuardActionRunning span
1[1,2]end 2 < start 4phase 1: copy(untouched)
2[3,5]3 <= 8phase 2: absorb[3,8]
3[6,7]6 <= 8phase 2: absorb[3,8]
4[8,10]8 <= 8, the boundaryphase 2: absorb[3,10]
5[12,16]12 > 10phase 3: copy restfinal: [[1,2],[3,10],[12,16]]

Step 4 is the whole question in one row: the new interval had already grown to [3,8], and [8,10] touches it at exactly 8, so the <= guard pulls it in and the span becomes [3,10]. Write < there instead and the output is [[1,2],[3,8],[8,10],[12,16]], two intervals sharing a boundary point in a list whose invariant says non-overlapping, and every downstream consumer of that invariant is now subtly broken. Also worth noticing in the trace: the span grows while absorbing (step 4 widens the end from 8 to 10 mid-loop), which is the "extends past several existing ones at once" behavior that the append-and-re-merge shortcut hides from you and the three-phase version makes explicit.

Tests, unprompted, hitting every phase boundary:

assert insert_interval([[1, 3], [6, 9]], [2, 5]) == [[1, 5], [6, 9]]
assert insert_interval([[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], [4, 8]) \
       == [[1, 2], [3, 10], [12, 16]]            # absorbs three intervals
assert insert_interval([], [5, 7]) == [[5, 7]]    # empty list
assert insert_interval([[1, 5]], [6, 8]) == [[1, 5], [6, 8]]   # after all
assert insert_interval([[3, 5]], [1, 2]) == [[1, 2], [3, 5]]   # before all
assert insert_interval([[1, 5]], [2, 3]) == [[1, 5]]           # fully contained
EXISTING NEW RESULT copy ends before new starts absorb into one span min of starts, max of ends copy starts after new ends Three phases, one pass, no sort. The bug is always the boundary: does touching count as overlapping?

What interviewers probe next (1) Many inserts over time? A list makes each insert O(n); discuss an interval tree or a balanced BST keyed by start for O(log n). Naming the structure and its tradeoff (complexity vs. how many inserts you actually expect) is enough. (2) Delete/subtract an interval? Same three-phase shape, but phase 2 may split an interval into two; sketch it. (3) Booking system semantics: Palantir flavors this as "can this meeting be scheduled?", which is just phase 2 returning a boolean.

Common mistakes Off-by-one on the guards: intervals[i][1] < start vs <= changes whether touching intervals merge; tie it back to the boundary semantics you clarified. Forgetting to append the merged interval when the input list is empty or when the new interval lands after everything (the append outside both loops handles both; point that out). Re-deriving overlap logic by patching failing cases live, which reads as guessing. And jumping straight to the interval-tree answer for a single insert: premature generality is a real scoring deduction in practical loops.

That one was free — and so are 10 answers per topic without an account. Signing in doubles that to 20, opens the Plus lessons in the courses, and remembers which topics you keep getting wrong.no card · Google sign-in · nothing to cancel
HOW DID IT GO?
0
READING SIGNED OUT

Signing in doubles your free answers, from 10 to 20 per topic, and the site starts remembering you: mastery per topic, bookmarks, and a next-focus recommendation. Free, no card.

Sign in free
UP NEXT ON YOUR JOURNEY
FEDITOR'S NOTE

The tempting wrong move is to append the new interval, re-sort, and re-run merge, which works but throws away the sorted invariant the question hands you for free; interviewers notice when you ignore a given. The clean answer is the three-phase scan (intervals entirely before, the overlapping run you absorb, intervals entirely after), and the phase candidates botch is the boundary where the new interval extends past several existing ones at once.

DISCUSSION · 0

No comments yet — be the first to share your approach.