skip to content

In-place destructive sweeps or a fresh output buffer — how do you set that policy for a team?

level: principalimportance: should knowfreq 34%

answer

  1. who owns the buffer being mutated?
  2. what does the caller lose?
  3. retries re-read the same input
  4. measure before trading safety for memory
  5. a reusable scratch buffer is a third option

basics

~20 s

Decide by ownership and evidence, not taste. Allow a destructive sweep where a stage owns the buffer and a measured memory ceiling is real, with a loud contract; elsewhere prefer the copying version, which is asymptotically identical and cheaper to review.

solid answer

~50 s

Both shapes are linear in time; the difference is a linear amount of memory versus a class of aliasing defects. An in-place sweep saves a per-batch allocation the size of the input, which matters when workers run against a hard memory ceiling and the batch dominates their footprint. What it costs is the caller's ability to re-read its own input: retries, replays, and anything holding a second reference to that buffer now observe mutated data, and every call site has to be audited rather than reasoned about locally. My policy is that destructive sweeps live behind a boundary that owns the buffer it mutates, never on a shared utility surface; the signature must return the new length so truncation cannot be ignored, and the contract must say the input is consumed. Outside that boundary, copy — and revisit only with profile evidence that allocation, not parsing or persistence, is the constraint.

go deeper

for a junior

Understand that a sweep which rewrites the buffer it reads leaves the caller's original data gone. Ask who else uses that buffer before assuming mutation is harmless.

for a middle

Be able to compare the two shapes concretely: identical linear time, constant versus linear extra space, and a returned length that the caller must not drop.

for a senior

Show that you check retry and replay paths for re-reads of a consumed input, and that you would put the destructive version behind a boundary that owns the memory.

for a principal

Own the policy and its reversal criteria: name the ceiling or ownership fact that justifies mutation, design the contract so misuse is visible, and insist on profile evidence rather than a blanket rule in either direction.

## What is actually being traded A same-direction sweep that compacts, collapses or merges can either mutate the buffer it reads or write into a fresh one. Both are one linear pass. The real ledger looks like this: | | In-place destructive sweep | Fresh output buffer | |---|---|---| | Time | linear, one pass | linear, one pass plus a copy | | Auxiliary space | O(1) | O(n) | | Caller may re-read input | no | yes | | Local reasoning at call sites | no — aliasing is global | yes | | Failure mode when wrong | silent data loss | wasted memory | The asymmetry in the last row is the whole argument. Getting the copying version wrong wastes memory; getting the in-place version wrong produces plausible-looking output with entries destroyed, which is the kind of defect that reaches a ledger before it reaches a test. ## The constraints that make in-place the right call **A hard per-worker memory ceiling with a batch-dominated footprint.** If each ingest worker is capped and the incoming batch is most of what it holds, doubling the batch to merge it is not a rounding error — it halves the batch size the fleet can accept, which changes capacity planning, not just a benchmark. **Ownership.** The stage that runs the sweep allocated the buffer, no other component holds a reference to it, and the buffer dies at the end of the stage. Here "destructive" costs nothing, because nobody was ever coming back for the original. **A pre-sized buffer by design.** Where the buffer was deliberately allocated with headroom so a batch could be merged into it, refusing to write into that headroom throws away the reason it exists. ## The constraints that make copying the right call **Retries and replays.** The most expensive lesson in this area is a retry path that re-reads a buffer a previous attempt already compacted. The first attempt fails after the sweep, the retry sees mutated input, and the result is wrong in a way no stack trace explains. If the pipeline retries — and pipelines retry — a consumed input is a trap. **Shared or cached inputs.** Any buffer that might be handed to two consumers, memoised, or read again for metrics cannot be mutated by one of them. **Team surface area.** A destructive sweep is correct only while every reader remembers the aliasing rule. That memory decays with staffing. If the function sits on a shared utility surface where a newcomer will reach for it, the maintenance cost is not the code, it is every future reviewer having to prove nobody re-reads the input. ## Making the contract impossible to miss When in-place is chosen, the design work is in the interface, not the loop: - **Return the new length, and make it hard to discard.** The buffer alone is not the result; the prefix length is. A caller that drops the length silently keeps stale entries past the frontier. - **Name the operation for what it does to its argument** — compact, collapse, consume — rather than for what it returns. - **State the consumption in the contract** at the call surface, and add a test that asserts the source is mutated. A test that pins the destructive behaviour turns an implicit trap into a documented one. - **Keep it behind a boundary.** Expose the safe copying operation publicly and use the destructive sweep as the private implementation inside the stage that owns the memory. Callers get local reasoning; the hot path still gets the allocation saved. ## Order of operations Measure first. The copying version's extra pass is a linear write with a small constant, and in a pipeline that parses, validates and persists each record, it routinely disappears into the noise. Optimising it away before profiling trades a real correctness margin for an imagined gain — and the classic wrong answer here is "in-place is faster because it allocates less", stated without a measurement, ignoring that the copying version has excellent locality and that the allocation may be amortised by buffer reuse anyway. When the profile does implicate allocation churn, the cheapest fix is often neither: reuse one scratch buffer per worker across batches. That recovers most of the memory win while leaving the sweep non-destructive with respect to the caller's input, and it is the option teams skip because the two named alternatives crowd it out. ## What a strong answer sounds like Name the ceiling or the ownership boundary that justifies the choice; state what the caller loses; describe the interface that makes the loss visible; and commit to reversing the decision on evidence. A lead who mandates one shape everywhere — always in-place for speed, or never in-place on principle — has replaced a judgment with a slogan, and will be wrong on whichever side of the fleet does not match the slogan.

  • What evidence would make you reverse a copy-by-default policy?
    A profile showing allocation and memory pressure, not parsing or persistence, as the binding constraint on the hot path — and a ceiling that the batch size actually approaches. I would want the numbers per worker rather than aggregate, since the ceiling is per worker, and I would first try a reused scratch buffer, which recovers most of the win without consuming the caller's input.
  • How do you stop a destructive sweep from becoming a trap for the next person?
    Keep it private to the stage that owns the buffer, expose only a safe wrapper, return the surviving length so truncation cannot be dropped, name the operation after what it does to its argument, and write a test that asserts the source is mutated. The point is to make the aliasing contract something a reviewer reads rather than remembers.
  • Is the copying version genuinely slower in practice?
    Same asymptotic class — one linear pass either way — with an extra linear write and an allocation. In pipelines that parse and persist each record, that difference is usually invisible; the copy also has clean sequential locality. It becomes real when the batch is large relative to a per-worker memory ceiling, which is a capacity question rather than a latency one.

saying these in an interview costs you the question

  • Optimises allocation before profiling where time actually goes
  • Assumes in-place is always faster because it allocates less
  • Leaves the consumed-input contract undocumented at call sites
  • Calls the mutation a private detail while mutating a caller's buffer
  • Bans in-place everywhere despite a real per-worker memory ceiling
  • Ignores retry paths that re-read an already-swept buffer

context