Sieve of Eratosthenes
Find every prime up to n by crossing out multiples of each prime starting from its square, in O(n log log n).
Overview
The Sieve of Eratosthenes marks composites instead of testing primes. Start with every number 2..n unmarked. For each i from 2 upward, if i is still unmarked it is prime — cross out i², i² + i, i² + 2i, …. Stop the outer loop at √n; whatever remains unmarked is prime. Total work is O(n log log n), nearly linear.
Two upgrades are common. The smallest-prime-factor (SPF) sieve records, for every x ≤ n, its smallest prime divisor; with it, any x factors in O(log x) by repeatedly dividing by spf[x] — see Prime Factorization. The linear sieve (Euler's sieve) marks every composite exactly once via its smallest prime factor, achieving O(n) and producing the SPF table as a by-product.
Memory is a bit or byte per number: n = 10^7 fits in 10 MB of bytes or ~1.2 MB of bits. Beyond ~10^8, use a segmented sieve that processes windows of size √n with the small primes.
Intuition
A mental model before the formal terms.
Write the numbers 2 to 100 on a grid. Circle 2 and strike out every second number after 4. The next unstruck number, 3, is prime — strike every third number from 9. Then 5 from 25, 7 from 49. After 7 (7² = 49 ≤ 100 < 11² = 121) nothing more needs to be struck; the survivors are the 25 primes below 100. Every composite was hit by its smallest prime factor, which is at most √100.
How it works
- Allocate
isPrime[0..n]set to true; mark0and1false. - For
i = 2whilei·i ≤ n: ifisPrime[i], forj = i·i; j ≤ n; j += i:isPrime[j] = false. - Collect all
iwithisPrime[i]true. - SPF variant: allocate
spf[0..n] = 0. For eachiwithspf[i] == 0(prime), setspf[i] = iand for multiplesj = i·i .. nstepi, setspf[j] = ionly ifspf[j] == 0. - Linear sieve: keep a list
primes. Fori = 2..n: ifspf[i] == 0thenspf[i] = iand append. Then for each primepinprimeswithp ≤ spf[i]andi·p ≤ n:spf[i·p] = p. Each compositecis set exactly once, wheni = c / spf[c].
Why it works
Every composite c ≤ n has a prime factor p ≤ √c ≤ √n, so it is crossed out during the pass for p (or earlier). Every prime is never a multiple of a smaller prime, so it survives.
Starting at i² is safe because i·k for k < i was already crossed out by a prime factor of k, which is smaller than i.
Cost: the pass for prime p touches n/p cells. Summing n/p over primes p ≤ n gives n · Σ 1/p ≈ n · ln ln n (Mertens), so O(n log log n).
Linear sieve: the loop over primes stops at spf[i], so composite i·p is only written with p ≤ spf[i], i.e. p is its smallest prime factor. Each composite has one smallest prime factor, so it is written exactly once.
Recognition
How to tell a problem wants this.
- "Count primes ≤ n", "list primes", "is each of these
qnumbers prime" with many queries andn ≤ ~10^7. - Repeated factorization of many numbers — SPF sieve.
- Multiplicative functions over a range (Euler's φ, number of divisors, Möbius μ) — the linear sieve computes them alongside.
Interactive visualization
Play, step, change the input. ← → and space work too.
1isPrime = [true] * (n + 1)2for p in 2 .. floor(sqrt(n)):3 if isPrime[p]:4 for m in p*p, p*p+p, .. n:5 isPrime[m] = false6primes = [p for p in 2..n if isPrime[p]]Pseudocode
1isPrime[0..n] = true; isPrime[0] = isPrime[1] = false2for i = 2; i * i <= n; i++:3 if isPrime[i]:4 for j = i * i; j <= n; j += i:5 isPrime[j] = false6return [i for i in 2..n if isPrime[i]]Implementations
1import math2 3 4def sieve(n: int) -> list[int]:5 """Cross out every multiple of every prime. Finds all primes below n in6 O(n log log n) — effectively linear for any practical n."""7 if n < 2:8 return []9 101 · One flag per number; 0 and 1 are not prime by definition11 is_composite = bytearray(n)12 primes: list[int] = []13 14 for p in range(2, n):15 if is_composite[p]:16 continue17 primes.append(p)18 192 · Start at p*p — smaller multiples already have a smaller factor20 if p * p >= n:21 continue22 is_composite[p * p :: p] = b"\x01" * ((n - p * p + p - 1) // p)23 return primes24 25 263 · Smallest-prime-factor sieve: same cost, but factorises in O(log n)27def smallest_prime_factor(n: int) -> list[int]:28 spf = [0] * n29 for i in range(2, n):30 if spf[i] != 0:31 continue32 for m in range(i, n, i):33 if spf[m] == 0:34 spf[m] = i35 return spf36 37 384 · Segmented sieve: primes in [lo, hi) without allocating hi flags39def segmented_sieve(lo: int, hi: int) -> list[int]:40 limit = math.isqrt(hi) + 141 base = sieve(limit + 1)42 43 composite = bytearray(hi - lo)44 for p in base:45 start = max(p * p, -(-lo // p) * p) # -(-a // b) is ceiling division46 for m in range(start, hi, p):47 composite[m - lo] = 148 495 · Collect what survived, skipping 0 and 1 if the range includes them50 return [v for v in range(max(lo, 2), hi) if not composite[v - lo]]bytearray(n)is the dense flag array: one byte per entry, zero-initialised, mutable.is_composite[p * p :: p] = b"\x01" * countis the crucial Python idiom — slice assignment with a step crosses out every multiple in one C-level operation instead of a Python loop.- The count
(n - p*p + p - 1) // pis the number of slots the extended slice covers; slice assignment requires the right-hand side to match that length exactly. math.isqrt(hi)gives the exact integer square root with no float rounding, which matters whenhiexceeds 2^53.-(-lo // p) * pis ceiling division written with the negation trick, since Python//floors rather than truncates.
The slice-assignment trick moves the inner loop into C and is typically 5-10x faster than an explicit for m in range(...).
- Extended slice assignment on a
bytearrayis the fastest pure-Python sieve idiom;numpywitharr[p*p::p] = 1is faster still and reads the same. math.isqrtis exact for arbitrarily large integers, unlikeint(math.sqrt(n))which loses precision past 2^53.//is floor division, so-(-a // b)is the standard ceiling-division idiom;math.ceil(a / b)goes through a float and is inexact for huge values.sympy.primerangeandsympy.factorintare the library answers when correctness matters more than owning the code.
- Mis-computing the slice-assignment length, which raises
ValueError: attempt to assign bytes of size X to extended slice of size Y. - Using
int(math.sqrt(n))on a largenand getting a bound one too small, so a prime factor is missed. - Writing
math.ceil(lo / p)for hugelo, where the float division has already lost the low bits.
- Dense flags: C++
std::vector<char>, JS/TSUint8Array, Pythonbytearray— and in Python the crossing-out loop can be replaced entirely by extended slice assignment, which has no equivalent in the other three. - Exact integer square root exists only in Python (
math.isqrt); C++ and JS/TS go through adoublesqrt, which is exact only below 2^53. - Overflow of
p * pis a real hazard only in C++ with 32-bitint; JavaScript doubles are exact to 2^53 and Python integers are unbounded. - Ceiling division: C++
(a + b - 1) / b, JS/TSMath.ceil(a / b), Python-(-a // b)— three idioms for one operation, and only the Python one stays exact at arbitrary magnitude.
Complexity
Linear sieve is O(n) time. Segmented sieve reduces memory to O(sqrt n) plus one window. Trial division of a single number is O(sqrt n).
Compare growth rates in the Complexity Explorer →When to use — and when not to
- All primes up to
n ≤ ~10^7–10^8, or many primality queries in that range. - Factorizing many numbers quickly — precompute SPF once, then
O(log x)per number. - Computing multiplicative functions (φ, μ, divisor counts) for every number up to
n.
- A single large number (
10^12or10^18): use trial division to√n, Miller–Rabin, or Pollard rho — a sieve cannot allocate that range. - Only a few small queries:
O(√n)trial division per query is simpler and uses no memory. - A range
[L, R]with hugeL— use a segmented sieve over that window rather than sieving from 2.
Alternatives
Common mistakes
- Starting the inner loop at
2·iinstead ofi·i(correct but ~2× slower), or bounding the outer loop atninstead of√n. i * ioverflowing 32-bitintwhennis near2^31— cast to 64-bit or loop withi <= n / i.- Marking with a
List<Boolean>orArrayof boxed values in Java/JS — use primitive arrays or typed arrays; the sieve is memory-bound. - Forgetting to mark
0and1as non-prime. - Linear sieve: omitting the
p > spf[i]break, which makes itO(n log n)and marks composites multiple times.
Interview patterns
- Count Primes (LeetCode 204) — the sieve is the intended solution; trial division per number times out.
- Prime pairs with a target sum, or "closest prime numbers in range": sieve then scan.
- Number of distinct prime factors / divisors for all
x ≤ nvia SPF. - Sum of Euler's totient over
1..nwith the linear sieve.