skip to content

questions

10

Why does summing a contiguous array of sensor readings beat summing the same values through scattered references, when both are O(n)?

level: juniorimportance: must knowfreq 60%

answer

  1. count trips to memory, not additions
  2. memory arrives in blocks, not values
  3. one fetch brings many neighbouring readings
  4. one loop's next address is predictable
  5. stalls overlap versus stalls that serialise

basics

~20 s

Memory moves in fixed-size blocks, so one fetch of a contiguous array delivers many neighbouring readings at once and the hardware can run ahead and fetch the next block early. Scattered references pay a separate, unpredictable trip per value.

solid answer

~50 s

Both loops touch `n` values, so both are O(n) — the gap is entirely in the constant factor, and the constant factor is memory traffic. Main memory is not addressed one value at a time: a miss pulls in a whole cache line (about 64 bytes on mainstream hardware), so a sequential scan of 4-byte readings gets roughly sixteen of them per fetch and the remaining fifteen are essentially free. The address stream is also a simple forward walk, which the hardware prefetcher recognises and stays ahead of, so the loop rarely waits. Chasing scattered references inverts all of that: each line usually yields one useful value, the next address is unknown until the current load returns, so the misses serialise instead of overlapping. That is how you get a ~20x wall-clock ratio between two loops with identical complexity.

go deeper

for a junior

Be ready to say that memory is read in fixed-size blocks, so one fetch of a contiguous array delivers many neighbouring values while a scattered read delivers one. Name spatial locality, and be clear that both loops are still O(n).

for a middle

Explain the mechanics: cache-line granularity, a prefetcher that recognises a forward walk, and independent misses overlapping while a chain of dependent addresses serialises one full round trip after another.

for a senior

Show that you would measure rather than assert. Time both shapes across input sizes, find where the working set leaves cache, and confirm the loop is memory-bound before recommending any layout change.

for a principal

Own the tradeoff. A locality-driven layout buys real latency but leaks into call sites, tests and onboarding, so be ready to say when a hot-loop win justifies the maintenance the whole team absorbs and when it does not.

## What the question is really testing An interviewer asking this is not checking whether you can compute a complexity class. Both loops visit every reading exactly once and both are O(n); if that were the whole story, they would take the same time. The question is whether your complexity claims come from a mental model of memory or from a memorised table. A candidate with the model can explain a 20x gap without ever changing the O; a candidate without it says "they're both O(n), so they perform the same" and stops. ## Memory does not move one value at a time Processors read memory in fixed-size blocks called **cache lines** — 64 bytes on most mainstream hardware today. Ask for a single 4-byte reading and the machine fetches the whole 64-byte line containing it and installs that line in cache. There is no such thing as fetching four bytes from main memory. That single fact drives everything else: - A contiguous array of 4-byte readings packs **16 readings per line**. The loop pays one memory trip and then gets fifteen further readings from cache, each costing a couple of cycles instead of a couple of hundred. - Values reached through scattered references live wherever the allocator happened to put them. Each one lands in a different line, so the loop pays roughly **one memory trip per reading** and throws away most of every line it fetched. The ratio of useful bytes to fetched bytes is the whole game. This is what **spatial locality** means: if you touch an address, you are very likely to touch its neighbours soon, so the hardware bets on that and prefetches the neighbourhood. ## Prefetching and overlapped misses There is a second, equally large effect. Modern chips contain a **hardware prefetcher** that watches the address stream and, when it detects a simple pattern — a forward walk, a fixed stride — issues loads for the next lines before the program asks for them. A sequential scan is the easiest pattern in the world to predict, so by the time the loop needs line *k+1*, it has already arrived. Scattered access defeats this in two ways. First, the addresses look random, so there is no stride to detect. Second, and worse, the *next* address is stored inside the data you just loaded, so it cannot even be computed until the current fetch completes. That serialises the misses: latency, then latency, then latency, each round trip fully exposed. A contiguous scan issues many independent misses at once and overlaps their latencies (memory-level parallelism); a reference chase can only ever have one outstanding. Roughly: sequential access is limited by memory **bandwidth**, scattered access by memory **latency**, and latency has improved far less over the decades than bandwidth has. ## Why the same array can be laid out either way The two shapes in the question are not exotic. Runtimes make genuinely different structural choices here: systems languages such as C and Rust store an array of records as one contiguous block of inline bytes, while managed runtimes such as the JVM and CPython commonly store an array of *references* to separately allocated values. The logical structure — "an array of readings" — is the same; the memory behaviour is not. That is why "is it an array?" is a weaker question than "are the values themselves contiguous?" ## Where the effect appears and where it does not - **Small inputs hide it.** If every reading fits in cache, both loops read from cache and the gap shrinks to a small factor. The ratio widens as the working set outgrows each cache level, then stabilises once both are served from main memory. - **Compute-bound loops hide it.** If each element costs an expensive computation, memory latency overlaps with useful work and the layout matters much less. The 20x figure belongs to a loop that does almost nothing but add. - **Write-heavy or randomly-updated workloads** change the picture again, because scattered writes also dirty many lines. ## How to say it in an interview State the invariant first — *the complexity is the same; the constant factor is not, and constant factors are not noise* — then name the two mechanisms (cache-line granularity plus prefetching, and overlapped versus serialised misses), then say how you would confirm it: time both shapes across several input sizes and watch where the curve bends. Asserting "contiguous is faster" is a slogan; explaining *how many lines each loop touches* is an answer.

  • The contiguous loop is about 20x faster at 100 million readings but only about 3x faster at ten thousand. Why does the gap widen with size?
    At ten thousand readings the whole working set fits in cache, so both loops are served from fast memory and only the address unpredictability costs anything. As the data outgrows each cache level, the scattered walk starts paying full main-memory latency per value while the contiguous scan still amortises one fetch over sixteen values and keeps the prefetcher busy. The ratio grows until both are entirely main-memory bound, then flattens.
  • Does the contiguous layout still win if the loop does heavy arithmetic on each reading?
    Much less. The advantage is that the contiguous scan spends less time waiting on memory; if each element triggers, say, a transcendental computation, the processor has useful work to do while outstanding fetches complete, and the loop becomes compute-bound. The layout win shrinks toward nothing. That is why this question belongs to tight scan-and-accumulate loops, not to every loop in a program.
  • How would you demonstrate the claim rather than assert it in a design review?
    Build both shapes over the same values, time them across a sweep of input sizes from well inside cache to far outside it, and plot nanoseconds per element. A locality effect shows as a curve that is flat while the working set fits in cache and then steps up at each level boundary. Report the per-element numbers and the size where the curve bends, not a single headline multiplier.

Fetching memory is like a delivery truck that always brings a full pallet: if the next sixteen things you need are on the pallet, one trip serves them all; if each item lives in a different warehouse, you make sixteen trips and unload one item each time.

saying these in an interview costs you the question

  • Both loops are O(n), so they must perform the same
  • Treats every memory read as costing the same
  • Thinks the difference is allocation speed, not access cost
  • Claims the compiler optimises the layout difference away
  • Assumes prefetching helps any access pattern equally

context

open as a page

Why does an array give O(1) access to any index — what address computation makes element 5,000,000 one step away?

level: juniorimportance: must knowfreq 85%

basics

~20 s

An array stores equal-size elements contiguously, so the address of element i is base + i × element-size. That is one multiply and one add no matter how large i is, so element 5,000,000 costs the same as element 3.

open as a page

How can an out-of-bounds array write at index equal to the length silently corrupt neighbouring data instead of failing?

level: middleimportance: must knowfreq 60%

basics

~20 s

The address formula base + i × element-size works for any i, valid or not; index equal to the length lands one element past the array, on whatever is stored next. Unchecked runtimes just write there; checked runtimes compare the index against the stored length first and raise an error.

open as a page

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

level: middleimportance: must knowfreq 76%

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.

open as a page

Why does reading every 16th value of a contiguous capture buffer take nearly as long as reading all of them?

level: middleimportance: should knowfreq 42%

basics

~20 s

Memory is fetched in whole cache lines, not single values. A 16-element stride over 4-byte values lands on a new line every step, exactly as many lines as the full scan touches, so traffic is unchanged and only the arithmetic gets cheaper.

open as a page

Removing one element from an array: when may you swap with the last instead of shifting?

level: middleimportance: should knowfreq 50%

basics

~20 s

Only when the array's order carries no meaning. Swapping the last element into the vacated slot is constant time but permutes the array; shifting the tail left costs Θ(n − i) and is the only order-preserving option.

open as a page

A teammate calls your fixed-capacity array of 16-bit sensor samples primitive and says hardware bounds-checks it for free — how do you defend the design and correct the claim?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A fixed-capacity contiguous buffer gives a provable memory budget, no allocation in the hot path, and O(1) indexing — exactly what a constrained device needs. The hardware claim is false: memory protection is page-granular, so most out-of-bounds accesses land in mapped memory and trap nothing.

open as a page

A diff inserts each match result into a sorted score array per event — what do you flag in review?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Each insert opens a hole mid-array, so it costs linear time in the scores already stored. Across m events that compounds to quadratic total work — invisible at fifty players, a stall at fifty thousand.

open as a page

Structure-of-arrays vs array-of-records: why does splitting telemetry records into parallel field arrays speed up a one-field hot loop?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

One array per field means a loop reading a single field gets only that field's values in each fetched cache line, instead of dragging along every unused field of every record. Fewer lines touched, far less wasted bandwidth, same O(n).

open as a page