skip to content

In an array-backed alert feed, why is inserting at index 0 O(n) but appending O(1)?

level: middleimportance: must knowfreq 76%

answer

  1. Insertion opens a hole first
  2. Count the elements above the position
  3. Cost depends on where, not how many
  4. The front is the worst position
  5. Copy top-down or you smear values

basics

~20 s

An array's slots are fixed positions, so making room at index 0 means moving every existing element one slot right — n moves. Appending writes into the first free slot and moves nothing, so it costs one write.

solid answer

~50 s

Inserting into an array is not "adding an element"; it is "opening a hole", and the hole has to be paid for by shifting everything above it. Insert at position `p` in an array holding n items and elements `p` through `n-1` each move one slot right — that is `n - p` moves plus the one write for the new value, so the cost is Θ(n − p). Newest-first insertion picks the worst p: `p = 0` moves all n elements on every alert. Appending picks the best: `p = n` moves nothing — one write, O(1). The shift must also run top-down; copying upward from p overwrites each element before it is read, smearing one value across the tail. The usual fix is to stop fighting the layout: append at the end and read the feed backwards, which costs nothing and displays newest-first anyway.

code

pseudocode · 11 lines
pseudocode
// arr has capacity > n; items occupy arr[0..n-1]
// insert x at position p, 0 <= p <= n

for i in n-1 down to p:
    arr[i+1] = arr[i]      // n - p moves

arr[p] = x                 // 1 write
n = n + 1

// p == n  -> loop body never runs -> O(1)
// p == 0  -> every element moves  -> O(n)

go deeper

for a junior

Be ready to say that a front insert moves every element one slot and an append moves none, and to count the moves for a small example out loud rather than reciting O(n).

for a middle

Explain the cost as Θ(n − p) and why the copy runs top-down. Expect to be asked whether a middle insert is cheaper — answer in both constant-factor and complexity terms.

for a senior

Show the design instinct: newest-first is a read-order requirement, and solving it in the write path buys a linear cost per event for nothing. Name what you would bound or measure before rewriting.

for a principal

Own the call about when the positional layout stops being the right storage at all, and be able to argue for leaving a known-linear insert alone when the array is provably small and the alternative adds a structure someone must maintain.

## The operation is "make a hole", not "add an element" A static array is a run of numbered slots. Slot `k` means position `k` and nothing else — an element cannot be "between" slots, and slots cannot be renumbered. So inserting a new alert at the head of the feed is not a matter of putting one value somewhere; it is a matter of first vacating position 0, which means every element currently occupying positions 0…n−1 must relocate to 1…n. That is the whole answer, and the count follows directly. To insert at position `p` in an array holding n items with spare capacity: - elements at `p … n−1` each move one slot right → **n − p moves** - the new value is written into slot `p` → **1 write** - the logical size increases by one → **O(1)** So the cost is Θ(n − p), a function of *where*, not of *how many you added*. | insert position | elements moved | cost | |---|---|---| | `p = n` (append) | 0 | Θ(1) | | `p = n/2` (middle) | n/2 | Θ(n) | | `p = 0` (front) | n | Θ(n) | The middle row is the one people misread. Half the moves is a factor of two, and big-O discards constant factors, so a middle insert is in exactly the same class as a front insert. "Only half the array moves" is a real saving in wall-clock terms and no saving at all in complexity terms; be precise about which claim you are making. ## Why the shift runs from the top down The direction is not stylistic. Copying upward — taking slot `p` into `p+1`, then `p+1` into `p+2` — destroys data: after the first copy, slot `p+1` no longer holds its own value, so the second copy propagates the value from `p` again, and the loop paints the original element at `p` across every slot to its right. Running from the top down writes only into slots whose contents have already been copied to safety. This is the classic overlapping-copy hazard, and it is why the fragment iterates `n−1` down to `p`. ## The alert-feed trap A feed that must display newest-first is where this bites in practice, because the natural implementation reads exactly like the requirement: each arriving alert goes in at index 0 so the array is always in display order. Every alert then relocates the entire backlog. One alert into a 10,000-entry feed is 10,000 element moves to store a single record — and a burst of 1,000 alerts is on the order of ten million moves, all of it work that produces no information. The wrong answer this scenario is built to catch is "adding one element is O(1) wherever it lands". Adding one element is O(1) only where nothing has to move. Position is the whole variable. Three honest ways out, in the order a reviewer would suggest them: 1. **Append and read backwards.** Store in arrival order — Θ(1) per alert — and iterate from the end when rendering. Newest-first is a *view* concern, not a storage concern, and a backwards walk over an array costs the same as a forwards one. 2. **Bound the feed.** If only the most recent k alerts are ever shown, the array never grows past k, and even a bad insert position is bounded by a constant you chose. 3. **Change the structure.** If genuinely front-heavy insertion is the workload, a positional array is the wrong shape for it, and that is a structure-selection conversation rather than a micro-optimisation. ## Constant factors, and where they do not save you A shift moves adjacent slots, so it is a contiguous block move — one of the fastest things hardware does per element, often far faster than a loop of unrelated work of the same length. This is why a front insert into a 200-element array is imperceptible and why the bug survives code review and staging. It does not change the complexity: block-copying n elements is still linear in n, and the cost per alert grows in lockstep with the feed. A fast constant delays the pain until the feed gets big; it does not remove it. ## Two boundaries worth stating out loud First, all of this assumes the array has spare capacity. A truly fixed-size array that is full cannot accept an insert at all — the operation fails rather than being slow. How capacity gets extended when it runs out is a separate mechanism with its own cost story. Second, appending is O(1) *given* a free slot at the end and a maintained count of how many slots are in use. Without that count you would have to find the end first, and "find the end" over a plain array of values is a linear scan. ## Saying it correctly Say: insert cost is Θ(n − p) because the elements above the insertion point must each move one slot; the front is the maximum of that expression and the end is the minimum; the copy must proceed top-down to avoid overwriting; and newest-first display should be solved by iterating backwards rather than by inserting at index 0.

  • The feed must display newest-first. How do you get that without paying the front insert?
    Store in arrival order and render backwards. Appending is one write with no movement, and walking an array from the last used slot down to zero costs the same as walking it forwards, so the display requirement is satisfied by the read path rather than the write path. Ordering for humans is a view concern; making the storage layout mirror the display order is what created the linear insert in the first place.
  • Does inserting into the middle instead of the front improve the complexity?
    No. A middle insert moves about n/2 elements instead of n, which is a constant factor of two — real in wall-clock terms, invisible to big-O. Both are Θ(n). The only insertion position with a genuinely different class is the end, where zero elements move. Quoting the middle as cheaper in complexity terms is a common way to signal that constant factors and growth classes have been conflated.
  • Why must the shift loop copy from the top down rather than starting at the insertion point?
    Because source and destination overlap. Copying slot p into p+1 first destroys the value that lived at p+1, so the next step re-copies the same value onward and the original element at p ends up smeared across the whole tail. Starting at the last used slot and working down guarantees every slot is read before anything is written into it.
  • The array is full. What does an insert cost then?
    In a genuinely fixed-size array it does not cost anything — it fails, because there is no slot to open. Capacity is a hard boundary, not a performance cliff. Systems that appear to insert indefinitely are relying on a separate growth mechanism that allocates a larger run and copies the contents across, which is a different operation with its own cost story.

Seats in a numbered row are fixed. Squeezing a latecomer into seat 1 makes everyone shuffle one seat along; seating them at the far end disturbs nobody.

saying these in an interview costs you the question

  • Says adding one element is O(1) wherever it lands
  • Claims a middle insert is a better complexity class than a front insert
  • Thinks the array renumbers or relinks slots instead of moving data
  • Copies upward from the insertion point and smears a value
  • Assumes a fixed-size array grows itself when full
  • Dismisses the shift as free because the memory is contiguous

context