A nightly job sieves every prime below 10^7. What dominates its cost, and what breaks at 10^9?
answer
- separate the time cost from the space cost
- the sieve holds one flag per candidate
- count the bytes at the higher ceiling
- near-linear time does not bound memory
- eight flags can share one byte
basics
~20 sMemory dominates, not time. The sieve keeps one flag per candidate, so a byte-per-flag run costs about ten megabytes at 10^7 but a gigabyte at 10^9, while the near-linear marking work stays affordable. Raising the ceiling breaks the flag array first.
solid answer
~40 sSieving is Θ(n log log n) — near-linear — so 10^7 is milliseconds and 10^9 is still seconds; time is rarely the constraint. The flag array is. One byte per candidate is ~10 MB at 10^7 but ~1 GB at 10^9, and well before RAM runs out the strided writes fall out of cache and the job goes memory-latency bound. Fixes in increasing effort: pack one bit per flag (8x), store odd candidates only and special-case 2 (another 2x, taking 10^9 to roughly 60 MB), then segment — precompute base primes up to the square root of the ceiling and sieve fixed windows that fit in cache. Or stop precomputing: if only a handful of values are ever tested, per-number trial division needs no array at all.
go deeper
Know that the sieve needs one flag per candidate up to the ceiling, so its memory grows with the range while a single-number test needs none. Be able to estimate the array size for a given ceiling.
Explain both costs separately: near-linear marking work versus linear memory. Be able to state what bit-packing and skipping evens each save, and why 2 becomes a special case in the odds-only layout.
Diagnose which resource actually fails at a higher ceiling and pick the fix that matches — packing, halving, segmenting, or dropping precomputation. Show that you know cache behaviour, not just the asymptotic bound, sets the real throughput.
Own the build-versus-query tradeoff under a real constraint: a memory ceiling on the fleet, a batch window, and a team that has to maintain a segmented sieve someone clever wrote. Be ready to argue for the plain array when the ceiling is low enough that the complexity is not worth owning.
## Read the two costs separately A sieve has a **time** cost and a **space** cost, and interviewers ask this question because candidates reason about the first and forget the second. **Time** is Θ(n log log n) — the sum of `n/p` over primes `p <= n`, where the sum of `1/p` grows like `ln ln n`. The `log log` factor is effectively a small constant at any realistic scale, so the sieve is near-linear. Going from 10^7 to 10^9 multiplies the arithmetic by roughly 100 plus a whisker: tens of milliseconds becomes a few seconds. For a nightly batch job, that is nothing. **Space** is Θ(n) with a constant you choose, and it is the one that fails. The naive layout is one flag per candidate in the natural per-element size of a flag array, which on mainstream platforms is a byte: | ceiling | one byte per flag | one bit per flag | one bit, odds only | |---|---|---|---| | 10^7 | ~10 MB | ~1.25 MB | ~625 KB | | 10^8 | ~100 MB | ~12.5 MB | ~6 MB | | 10^9 | ~1 GB | ~125 MB | ~60 MB | At 10^7 every column is comfortable, which is exactly why the naive version ships. At 10^9 the first column is a gigabyte of resident memory for a batch job, and that is what breaks. This is one of the places where mainstream runtimes made visibly different calls on the same concept: some standard libraries offer a bit-packed sequence-of-booleans type while others store a full byte per element in the ordinary boolean array, so "one flag" costs 8x more in one ecosystem than another with identical source-level intent. Know which one you are on before you quote a memory number. ## The hidden second failure: cache Even when the array fits in RAM, the marking loop writes with stride `p`. For small primes the stride is tight and cache-friendly; for larger primes each write lands in a different cache line, and once the array exceeds last-level cache the job spends its time waiting on memory rather than computing. Measured throughput per candidate gets *worse* as `n` grows, even though the asymptotic bound says otherwise. This is the classic case of an asymptotically near-linear algorithm whose real-world constant is set by the memory hierarchy, and it is the strongest argument for segmentation independent of whether the array fits. ## The fixes, in order 1. **Bit-pack the flags.** One bit per candidate, index `i` living at word `i / 64` and bit `i % 64`. Eight times less memory, and eight times more candidates per cache line. The cost is slightly more work per access and code that is easier to get wrong. 2. **Store odd candidates only.** Every even number above 2 is composite, so index `i` can represent the value `2i + 1` and 2 is special-cased. Half the memory and half the marking work, at the price of an index mapping that must be right in both directions. 3. **Segment.** Sieve the base primes up to the square root of the ceiling with an ordinary small sieve — for a 10^9 ceiling that is primes below about 31,623, a few thousand of them, needing almost no memory. Then process the full range in windows sized to fit comfortably in cache; for each window, for each base prime, compute the first multiple inside the window and mark forward. Peak memory becomes the window plus the base primes rather than the whole range, and locality improves dramatically. Segmentation is also the only way to sieve a *high window* — say primes between 10^9 and 10^9 + 10^6 — without materialising everything below it. 4. **Do not precompute.** If the job actually tests a hundred specific values, a square-root trial division per value costs about 31,623 divisions each near 10^9 and needs zero memory. The sieve earns its array only when the query count is large relative to the range. ## How to answer this in an interview Say the two costs separately, then commit to a number. "Time is near-linear, so 10^9 is seconds — fine for a nightly job. Space is one flag per candidate, so a byte per flag is a gigabyte, and that is what breaks first. I would go bit-packed and odd-only, which lands around sixty megabytes, and if the ceiling keeps climbing or I only need a high window, segment it." That answer demonstrates the thing the question is actually probing: that you know a near-linear time bound does **not** mean an algorithm scales, because the resource that fails may be the other one. ## The trap to avoid The wrong answer is "`n log log n` is basically linear, so I would just raise the constant and let it run." It is technically true about the arithmetic and completely wrong about the job, which will be killed by the memory limit long before it finishes. The related wrong answer is reaching for a dynamic collection of discovered primes instead of a flat flag array — that replaces a compact bitmap with per-element overhead and pointer chasing, making the memory problem several times worse while also destroying locality.
- How much does sieving only odd candidates actually save?It halves both memory and marking work. Index `i` represents the value `2i + 1`, 2 is handled as a special case, and no even value is ever stored or marked. Combined with one bit per flag it takes a 10^9 ceiling from about a gigabyte to roughly sixty megabytes. The cost is an index mapping you must apply consistently in both directions.
- When is a segmented sieve worth the extra code?When the flag array no longer fits in memory or in cache, and whenever you need a high window rather than everything from 2. Sieve base primes up to the square root of the ceiling, then walk fixed windows, marking each window with those base primes. Peak memory becomes window plus base primes, and locality improves enough that throughput per candidate often rises even when the whole array would have fit.
- The job only needs to test about a hundred values near 10^9. Would you still sieve?No. Square-root trial division costs roughly 31,623 divisions per value and no memory at all, so a hundred values is trivial. A sieve pays a range-sized array to answer questions you were never asked. Precomputation wins when the query volume is large relative to the ceiling; below that crossover the simpler per-query test is both faster end to end and cheaper to operate.
- Why can throughput per candidate get worse as the ceiling grows even though the bound is near-linear?Because the marking loop writes with stride equal to the prime. Once the flag array exceeds last-level cache, writes for larger primes each touch a different cache line and the job becomes memory-latency bound. The asymptotic bound counts writes, not their cost, so the real constant degrades. Segmentation restores locality by keeping the working window inside cache.
saying these in an interview costs you the question
- Says near-linear time means the ceiling can be raised freely
- Quotes a memory figure without saying bits or bytes per flag
- Stores discovered primes in a dynamic collection instead of a flat flag array
- Ignores cache locality and predicts constant throughput per candidate
- Precomputes a whole range to answer a handful of queries