Count Primes (LeetCode 204)
You receive one integer n. Count how many prime numbers sit below n, and hand that count back.
Recall that a prime is any whole number bigger than 1 whose only positive divisors are 1 and the number itself.
Worked Examples
n = 104n = 000.n = 101.⚖️Formal Constraints & Bounds
0 <= n <= 5 * 106
Core Insight
Never trial divide every candidate up to n. Use a boolean sieve array where crossing off multiples starting at p^2 eliminates composites in O(N log log N) near-linear time.
Real-World Scenario & Production Applications
Cryptographic prime generation for RSA key exchanges, hashing algorithms, and pseudo-random prime factor generation.
Subproblems & Recurrence Decomposition3 Phases
Allocate a boolean array is_prime = [True] * n where is_prime[0] = is_prime[1] = False since 0 and 1 are not prime by definition.
is_prime = [False, False] + [True] * (n - 2) if n > 2 else [False] * nStep-by-Step Execution Trace Table
Candidate Prime p | Is is_prime[p] True? | Action | Multiples Eliminated (p*p, p*p + p, ... < n=10) | Sieve State (Indices 0..9) |
|---|---|---|---|---|
| 2 | True | Eliminate multiples starting at | 4, 6, 8 | [F, F, T, T, F, T, F, T, F, T] |
| 3 | True | Eliminate multiples starting at | 9 | [F, F, T, T, F, T, F, T, F, F] |
| — | Sieve loop terminates | — | Remaining True: 2, 3, 5, 7 (Count: 4) |
The Math, Step by Step
1. The table the code fills
The code keeps a list is_prime of length , one entry for each number from 0 to . At the end, is_prime[m] is True exactly when is prime. The answer is the number of True entries, sum(is_prime).
A number is composite when it is not prime, so it has a factor other than 1 and . The code starts with every entry True, sets 0 and 1 to False, and then crosses out composite numbers: it sets their entries to False. This method is the Sieve of Eratosthenes.
2. What one prime crosses out
For one prime , the inner loop range(p * p, n, p) crosses out
Each of these is times a whole number of at least , so each is composite.
3. Why starting at and stopping at loses nothing
Start at . Take a multiple of below , say with . The number has a prime factor . So is a multiple of the smaller prime , and the loop for already crossed it out.
Stop at . Every composite can be written as with . Then , so . The smallest prime factor of is at most , so . Also with , so . So the loop for crosses out . Every composite below is crossed out by a prime with . Primes above need no loop of their own.
The if is_prime[p] check. When the outer loop reaches , every composite below is already crossed out, by the argument above. So is_prime[p] is True exactly when is prime. A composite is skipped, because its multiples are also multiples of its prime factors.
4. The bounds in the code
range(2, int(n ** 0.5) + 1) runs up to , including it. The + 1 is needed because range stops one before its end. For LeetCode's inputs, , int(n ** 0.5) equals the exact integer square root for every , so the float square root never cuts off a needed .
The early return if n <= 2: return 0 covers the inputs with no prime below . It also keeps is_prime[1] a valid index: the list needs at least two entries for the line is_prime[0] = is_prime[1] = False.
The problem asks for primes less than , so itself is never in the list. range(p * p, n, p) stops before for the same reason.
5. Example 1 by hand
n = 10, so is_prime has entries 0 to 9, and runs from 2 to .
is_prime[p] | Crossed out by range(p * p, 10, p) | |
|---|---|---|
| 2 | True | 4, 6, 8 |
| 3 | True | 9 |
After the loop, the entries still True are 2, 3, 5 and 7. The number 6 is a multiple of both 2 and 3. The loop for 3 starts at 9, so 6 is crossed out only once, by the loop for 2. The code returns 4, the expected output.
The picture shows the ten entries of is_prime after the loop, one cell per number from 0 to 9, with the number under its cell. "T" is True and "F" is False. Entries 0 and 1 are False because the code sets them before the loop. The label "p=2" marks the entries 4, 6 and 8, which the loop for crosses out. The label "p=3" marks entry 9, which the loop for crosses out. The four entries still True are 2, 3, 5 and 7, and the final sum(is_prime) counts them.
6. Example 2 by hand
n = 0. The condition n <= 2 is true, so the code returns 0 at once, the expected output.
7. How much work the crossing out takes
The loop for a prime writes at most entries. So the total work is about times the sum of over the primes . That sum grows like , a known result about primes (Mertens' second theorem). So the sieve takes time, and the list takes space. For , the loops write entries, about 2.1 writes per number.
8. From the math to the code
| Math | In countPrimes |
|---|---|
| no prime below 2 | if n <= 2: followed by return 0 |
| one entry for each from 0 to | is_prime = [True] * n |
| 0 and 1 are not prime | is_prime[0] = is_prime[1] = False |
| up to | for p in range(2, int(n ** 0.5) + 1): |
| only primes cross out | if is_prime[p]: |
| below | for multiple in range(p * p, n, p): |
| count the primes | return sum(is_prime) |
| 1 | if n <= 2: |
| 2 | return 0 |
| 3 | is_prime = [True] * n |
| 4 | is_prime[0] = is_prime[1] = False |
| 5 | for p in range(2, int(n ** 0.5) + 1): |
| 6 | if is_prime[p]: |
| 7 | mark every multiple of p, from p * p up, as not prime |
| 8 | return sum(is_prime) |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Checking every number for primality one at a time (trial division) is the obvious approach — and also the slow one. This algorithm never tests whether a number is prime; it only crosses off numbers it can already prove are not.
📅 The Analogy: Crossing Off a Giant Calendar
Picture every integer from 2 to n written on a giant wall calendar, all initially assumed prime. Starting at 2, you cross off every multiple of 2 (4, 6, 8, ...) — none of those can possibly be prime, since they're all divisible by 2. The next number still standing, uncrossed, must be prime (nothing smaller crossed it off) — so you keep it, then cross off all of its multiples too. Repeat: keep the next survivor, cross off its multiples, and so on. Whatever is still standing uncrossed at the end was never a multiple of anything smaller than itself — which is the definition of prime.
🔑 The Key Idea in the Code
- Trick 1 (start crossing at
p * p, not2 * p): Every smaller multiple ofp(like2p,3p, ...,(p-1)p) was already crossed off earlier, by a smaller prime that divides it.p * pis the first multiple ofpthat no smaller prime could have already reached. - Trick 2 (stop at
√n): Any composite number≤ nmust have a prime factor≤ √n— otherwise multiplying its two smallest factors together would already exceedn. So onceppasses√n, every remaining composite has already been crossed off by a smaller prime.
Problem asks for primes strictly less than : The problem statement specifies primes
< n, not . Allocating a sieve of size and not counting index is required.Starting multiple marking loop from instead of : All composites smaller than that have as a factor have already been marked by smaller prime factors. Starting the inner loop from avoids redundant iterations.
Edge cases : There are 0 primes strictly less than 2. Forgetting to guard
if n <= 2: return 0causes out-of-bounds or off-by-one errors.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Counting primes strictly less than N, sub-quadratic prime enumeration without repeated trial division.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: Every composite number m < N has a prime factor p <= √m. Marking multiples pp, pp + p, ... eliminates all composites efficiently.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Clear
is_prime[0]andis_prime[1]before sieving: no crossing reaches them, son = 10would count 6 instead of 4.Primes strictly below
n: the table is[True] * n(indices 0 to n - 1), andn <= 2returns 0 first, since forn = 0or1the table has no index 1 to clear.The outer bound is
int(n ** 0.5) + 1: without the+ 1,pnever reachessqrt(n), son = 5counts 4 as prime.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I'd use the Sieve of Eratosthenes, because I need an answer for every number below n, not just one. I make
is_prime, a list of n booleans set to True, then setis_prime[0]andis_prime[1]to False. For eachpfrom 2 up to the square root of n, ifpis still marked prime, I cross off its multiples starting atp * p, stepping byp. Starting there is safe because a smaller multiplep * kwithk < pwas already crossed off by a prime factor ofk, and stopping at the square root is safe because every composite below n has a prime factor no larger than its square root. The trap is 0 and 1: no crossing ever reaches them, so if I don't set them to False first, the count comes out two too high. Summingis_primegives the answer in O(N log log N) time andO(N)space."
Complexity & Mathematical Proof
O(N log log N)
Look at the nested loop: The outer loop runs for prime p <= sqrt(n). For each prime p, the inner loop marks multiples p^2, p^2 + p, p^2 + 2p... up to n. The total operations equal n times the sum of 1/p over every prime p <= sqrt(n). By Mertens' Second Theorem, the sum of reciprocals of primes diverges as log(log N). Multiplying by n yields strictly O(N log log N) time.
O(N)
Look at our memory allocations: We allocate a contiguous boolean array is_prime of length n. The call stack uses O(1) iterative space. Auxiliary memory is strictly O(N) bytes.
Derivation Progression
Multiples smaller than p^2 have already been marked by smaller prime factors, so inner loop begins directly at p^2.
The sum of 1/p over the primes never converges: by Mertens' second theorem it grows like log log N, which is why the total is only a small factor above N.
Near-linear time complexity that evaluates millions of integers in milliseconds.
Variable Definitions
Upper bound integer limit
Memory Architecture & Bounds
O(1) Iterative double for-loop
O(N) Contiguous boolean array is_prime of size N
O(1) Returns scalar count of primes
Boundary Best / Worst Cases
for N <= 2.
.
full sieve pass.
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
Trigger phrase: "count the number of prime numbers strictly less than n". Naive trial division for each integer takes , which executes operations and inevitably causes Time Limit Exceeded. A Senior SWE immediately applies the Sieve of Eratosthenes.
. Time budget must be sub-; the sieve's is only a small factor above . Memory is the real limit: [True] * n holds one 8-byte pointer per slot in CPython (about 40 MB for ), while a bytearray(n) needs one byte per slot (about 5 MB).
The outer prime scan only needs to run up to ; running it to stays correct but wastes checks. For very large , int(n ** 0.5) goes through a float and can round the wrong way; math.isqrt(n) is always exact.
Core Algorithmic State Invariants
- Memory Is the Limit at Scale
For n = 5 * 10^6 the time is small, but a Python list of booleans stores an 8-byte pointer per slot (about 40 MB). A bytearray stores one byte per slot (about 5 MB), and past one machine's memory a segmented sieve crosses one block at a time with the primes up to sqrt(n).
Count Primes (LeetCode 204)
You receive one integer n. Count how many prime numbers sit below n, and hand that count back.
Recall that a prime is any whole number bigger than 1 whose only positive divisors are 1 and the number itself.
Worked Examples
n = 104n = 000.n = 101.⚖️Formal Constraints & Bounds
0 <= n <= 5 * 106
Core Insight
Never trial divide every candidate up to n. Use a boolean sieve array where crossing off multiples starting at p^2 eliminates composites in O(N log log N) near-linear time.
Real-World Scenario & Production Applications
Cryptographic prime generation for RSA key exchanges, hashing algorithms, and pseudo-random prime factor generation.
Subproblems & Recurrence Decomposition3 Phases
Allocate a boolean array is_prime = [True] * n where is_prime[0] = is_prime[1] = False since 0 and 1 are not prime by definition.
is_prime = [False, False] + [True] * (n - 2) if n > 2 else [False] * nStep-by-Step Execution Trace Table
Candidate Prime p | Is is_prime[p] True? | Action | Multiples Eliminated (p*p, p*p + p, ... < n=10) | Sieve State (Indices 0..9) |
|---|---|---|---|---|
| 2 | True | Eliminate multiples starting at | 4, 6, 8 | [F, F, T, T, F, T, F, T, F, T] |
| 3 | True | Eliminate multiples starting at | 9 | [F, F, T, T, F, T, F, T, F, F] |
| — | Sieve loop terminates | — | Remaining True: 2, 3, 5, 7 (Count: 4) |
The Math, Step by Step
1. The table the code fills
The code keeps a list is_prime of length , one entry for each number from 0 to . At the end, is_prime[m] is True exactly when is prime. The answer is the number of True entries, sum(is_prime).
A number is composite when it is not prime, so it has a factor other than 1 and . The code starts with every entry True, sets 0 and 1 to False, and then crosses out composite numbers: it sets their entries to False. This method is the Sieve of Eratosthenes.
2. What one prime crosses out
For one prime , the inner loop range(p * p, n, p) crosses out
Each of these is times a whole number of at least , so each is composite.
3. Why starting at and stopping at loses nothing
Start at . Take a multiple of below , say with . The number has a prime factor . So is a multiple of the smaller prime , and the loop for already crossed it out.
Stop at . Every composite can be written as with . Then , so . The smallest prime factor of is at most , so . Also with , so . So the loop for crosses out . Every composite below is crossed out by a prime with . Primes above need no loop of their own.
The if is_prime[p] check. When the outer loop reaches , every composite below is already crossed out, by the argument above. So is_prime[p] is True exactly when is prime. A composite is skipped, because its multiples are also multiples of its prime factors.
4. The bounds in the code
range(2, int(n ** 0.5) + 1) runs up to , including it. The + 1 is needed because range stops one before its end. For LeetCode's inputs, , int(n ** 0.5) equals the exact integer square root for every , so the float square root never cuts off a needed .
The early return if n <= 2: return 0 covers the inputs with no prime below . It also keeps is_prime[1] a valid index: the list needs at least two entries for the line is_prime[0] = is_prime[1] = False.
The problem asks for primes less than , so itself is never in the list. range(p * p, n, p) stops before for the same reason.
5. Example 1 by hand
n = 10, so is_prime has entries 0 to 9, and runs from 2 to .
is_prime[p] | Crossed out by range(p * p, 10, p) | |
|---|---|---|
| 2 | True | 4, 6, 8 |
| 3 | True | 9 |
After the loop, the entries still True are 2, 3, 5 and 7. The number 6 is a multiple of both 2 and 3. The loop for 3 starts at 9, so 6 is crossed out only once, by the loop for 2. The code returns 4, the expected output.
The picture shows the ten entries of is_prime after the loop, one cell per number from 0 to 9, with the number under its cell. "T" is True and "F" is False. Entries 0 and 1 are False because the code sets them before the loop. The label "p=2" marks the entries 4, 6 and 8, which the loop for crosses out. The label "p=3" marks entry 9, which the loop for crosses out. The four entries still True are 2, 3, 5 and 7, and the final sum(is_prime) counts them.
6. Example 2 by hand
n = 0. The condition n <= 2 is true, so the code returns 0 at once, the expected output.
7. How much work the crossing out takes
The loop for a prime writes at most entries. So the total work is about times the sum of over the primes . That sum grows like , a known result about primes (Mertens' second theorem). So the sieve takes time, and the list takes space. For , the loops write entries, about 2.1 writes per number.
8. From the math to the code
| Math | In countPrimes |
|---|---|
| no prime below 2 | if n <= 2: followed by return 0 |
| one entry for each from 0 to | is_prime = [True] * n |
| 0 and 1 are not prime | is_prime[0] = is_prime[1] = False |
| up to | for p in range(2, int(n ** 0.5) + 1): |
| only primes cross out | if is_prime[p]: |
| below | for multiple in range(p * p, n, p): |
| count the primes | return sum(is_prime) |
| 1 | if n <= 2: |
| 2 | return 0 |
| 3 | is_prime = [True] * n |
| 4 | is_prime[0] = is_prime[1] = False |
| 5 | for p in range(2, int(n ** 0.5) + 1): |
| 6 | if is_prime[p]: |
| 7 | mark every multiple of p, from p * p up, as not prime |
| 8 | return sum(is_prime) |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Checking every number for primality one at a time (trial division) is the obvious approach — and also the slow one. This algorithm never tests whether a number is prime; it only crosses off numbers it can already prove are not.
📅 The Analogy: Crossing Off a Giant Calendar
Picture every integer from 2 to n written on a giant wall calendar, all initially assumed prime. Starting at 2, you cross off every multiple of 2 (4, 6, 8, ...) — none of those can possibly be prime, since they're all divisible by 2. The next number still standing, uncrossed, must be prime (nothing smaller crossed it off) — so you keep it, then cross off all of its multiples too. Repeat: keep the next survivor, cross off its multiples, and so on. Whatever is still standing uncrossed at the end was never a multiple of anything smaller than itself — which is the definition of prime.
🔑 The Key Idea in the Code
- Trick 1 (start crossing at
p * p, not2 * p): Every smaller multiple ofp(like2p,3p, ...,(p-1)p) was already crossed off earlier, by a smaller prime that divides it.p * pis the first multiple ofpthat no smaller prime could have already reached. - Trick 2 (stop at
√n): Any composite number≤ nmust have a prime factor≤ √n— otherwise multiplying its two smallest factors together would already exceedn. So onceppasses√n, every remaining composite has already been crossed off by a smaller prime.
Problem asks for primes strictly less than : The problem statement specifies primes
< n, not . Allocating a sieve of size and not counting index is required.Starting multiple marking loop from instead of : All composites smaller than that have as a factor have already been marked by smaller prime factors. Starting the inner loop from avoids redundant iterations.
Edge cases : There are 0 primes strictly less than 2. Forgetting to guard
if n <= 2: return 0causes out-of-bounds or off-by-one errors.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Counting primes strictly less than N, sub-quadratic prime enumeration without repeated trial division.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: Every composite number m < N has a prime factor p <= √m. Marking multiples pp, pp + p, ... eliminates all composites efficiently.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Clear
is_prime[0]andis_prime[1]before sieving: no crossing reaches them, son = 10would count 6 instead of 4.Primes strictly below
n: the table is[True] * n(indices 0 to n - 1), andn <= 2returns 0 first, since forn = 0or1the table has no index 1 to clear.The outer bound is
int(n ** 0.5) + 1: without the+ 1,pnever reachessqrt(n), son = 5counts 4 as prime.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I'd use the Sieve of Eratosthenes, because I need an answer for every number below n, not just one. I make
is_prime, a list of n booleans set to True, then setis_prime[0]andis_prime[1]to False. For eachpfrom 2 up to the square root of n, ifpis still marked prime, I cross off its multiples starting atp * p, stepping byp. Starting there is safe because a smaller multiplep * kwithk < pwas already crossed off by a prime factor ofk, and stopping at the square root is safe because every composite below n has a prime factor no larger than its square root. The trap is 0 and 1: no crossing ever reaches them, so if I don't set them to False first, the count comes out two too high. Summingis_primegives the answer in O(N log log N) time andO(N)space."
Complexity & Mathematical Proof
O(N log log N)
Look at the nested loop: The outer loop runs for prime p <= sqrt(n). For each prime p, the inner loop marks multiples p^2, p^2 + p, p^2 + 2p... up to n. The total operations equal n times the sum of 1/p over every prime p <= sqrt(n). By Mertens' Second Theorem, the sum of reciprocals of primes diverges as log(log N). Multiplying by n yields strictly O(N log log N) time.
O(N)
Look at our memory allocations: We allocate a contiguous boolean array is_prime of length n. The call stack uses O(1) iterative space. Auxiliary memory is strictly O(N) bytes.
Derivation Progression
Multiples smaller than p^2 have already been marked by smaller prime factors, so inner loop begins directly at p^2.
The sum of 1/p over the primes never converges: by Mertens' second theorem it grows like log log N, which is why the total is only a small factor above N.
Near-linear time complexity that evaluates millions of integers in milliseconds.
Variable Definitions
Upper bound integer limit
Memory Architecture & Bounds
O(1) Iterative double for-loop
O(N) Contiguous boolean array is_prime of size N
O(1) Returns scalar count of primes
Boundary Best / Worst Cases
for N <= 2.
.
full sieve pass.
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
Trigger phrase: "count the number of prime numbers strictly less than n". Naive trial division for each integer takes , which executes operations and inevitably causes Time Limit Exceeded. A Senior SWE immediately applies the Sieve of Eratosthenes.
. Time budget must be sub-; the sieve's is only a small factor above . Memory is the real limit: [True] * n holds one 8-byte pointer per slot in CPython (about 40 MB for ), while a bytearray(n) needs one byte per slot (about 5 MB).
The outer prime scan only needs to run up to ; running it to stays correct but wastes checks. For very large , int(n ** 0.5) goes through a float and can round the wrong way; math.isqrt(n) is always exact.
Core Algorithmic State Invariants
- Memory Is the Limit at Scale
For n = 5 * 10^6 the time is small, but a Python list of booleans stores an 8-byte pointer per slot (about 40 MB). A bytearray stores one byte per slot (about 5 MB), and past one machine's memory a segmented sieve crosses one block at a time with the primes up to sqrt(n).