In our last lesson, we developed an efficient O(sqrt(n)) algorithm to determine if a single number is prime. This is a great tool, but in competitive programming, you're often faced with problems that require checking primality for many numbers at once, or finding all primes within a large range. Running our trial division algorithm for each number would be too slow and lead to the dreaded "Time Limit Exceeded" verdict.
Today's lesson addresses this exact scenario. We will explore the Sieve of Eratosthenes, a classic and highly efficient algorithm designed for pre-computing prime numbers up to a given limit N. By the end of this lesson, you will be able to implement this powerful technique, a fundamental component of any competitive programmer's number theory toolkit.
The Need for a Better Approach: From Single Queries to Pre-computation
Imagine a problem where you're given up to queries, and for each query, you need to check if a number (up to ) is prime. Using the O(sqrt(n)) method from our previous lesson would result in roughly operations in the worst case. This is far too slow for a typical time limit of one second (which allows for about operations).
This problem structure—many queries on a fixed range of data—is a strong signal that pre-computation is the right strategy. The idea is to do some initial work to build a data structure that can then answer each query very quickly. The Sieve of Eratosthenes is a perfect example of this.
To see this scenario explained in the context of a competitive programming problem, watch the following segment from the CodeBeyond channel.
L2 | Sieve of Eratosthenes | Raj (Striver) | Prime Numbers for CP
This video by Raj (Striver) sets up the exact problem we are trying to solve: handling multiple primality queries efficiently. It clearly demonstrates why the previous lesson's approach is insufficient here.
Watch from the beginning up to the explanation of why a T * sqrt(N) approach would result in a Time Limit Exceeded (TLE) error and how pre-computation using a "black box" can solve this.
The Sieve of Eratosthenes: The Core Idea
The Sieve of Eratosthenes works on a simple, elegant principle. Instead of testing each number for primality, we take a different view: if a number is composite, it must be a multiple of some smaller prime number.
The algorithm proceeds as follows:
- Create a boolean array, let's call it
is_prime, of sizeN+1. Initialize all entries from 2 toNastrue, assuming every number is a potential prime. Mark 0 and 1 asfalse. - Start with the first prime number,
p = 2. - Mark all multiples of
p(i.e.,2*p,3*p,4*p, ...) up toNasfalsein theis_primearray, because they are clearly not prime. - Find the next number greater than
pthat is still markedtrue. This number is the next prime. Let this be our newp. - Repeat steps 3 and 4 until you have processed all necessary primes.
The numbers that remain marked true at the end are all the prime numbers up to N. This process is like sifting out the composite numbers, leaving only the primes behind.

For a dynamic walkthrough of this process, the same video provides an excellent dry run.
L2 | Sieve of Eratosthenes | Raj (Striver) | Prime Numbers for CP
This part of the video provides a step-by-step visual dry run of the sieve algorithm for N=25, which will help solidify your understanding of how the marking process works.
Watch the segment from the dry run. Observe how the boolean array is updated as the algorithm iterates through the primes 2, 3, and 5, marking their multiples as not prime.
From Naive Sieve to Optimized Implementation
A direct translation of the idea above would work, but we can make two crucial optimizations that are standard practice in competitive programming. For a concise explanation and C++ implementation, we'll turn to the cp-algorithms website, a vital resource for competitive programmers.
This article explains the algorithm, the C++ implementation, and its key optimizations.
First, read the introductory paragraphs from the start of the article to reinforce the core concept. Then, jump to the Implementation section. Carefully study the provided C++ code. The text right after the code block explains the first key optimization: starting the inner loop from i*i.
Let's break down that first optimization:
- Optimization 1: Start marking from
i*i
When we are processing a primei, why do we start marking its multiples fromi*iinstead of2*i? Consider a multiple likek*iwherek < i. This numberk*imust have a prime factor that is less than or equal tok. Sincek < i, this prime factor is also less thani. This means the numberk*iwould have already been marked as composite when we processed that smaller prime factor. For example, wheni=5, we don't need to mark2*5=10(marked by 2),3*5=15(marked by 3), or4*5=20(marked by 2). The first multiple we need to consider is5*5=25.
This leads us to the second major optimization.
This section details the second major optimization, which significantly reduces the number of iterations in the outer loop.
Read the subsection Sieving till root. Pay close attention to the updated for loop condition in the code.
- Optimization 2: Outer loop only needs to run up to
sqrt(N)
This follows directly from the first optimization. Since the inner loop starts marking ati*i, ifiis greater thansqrt(N), theni*iwill be greater thanN. The inner loop would do no work at all. Therefore, we only need to run the outer loop for primesiup tosqrt(N).
Putting these together gives us the standard, efficient implementation of the Sieve:
void sieve(int n) {
vector<bool> is_prime(n + 1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; i * i <= n; i++) {
if (is_prime[i]) {
for (int j = i * i; j <= n; j += i)
is_prime[j] = false;
}
}
// Now is_prime[k] is true if k is prime, false otherwise.
}
Complexity and Practical Considerations
-
Time Complexity: The optimized Sieve of Eratosthenes runs in
O(N log log N)time. The proof is based on the prime harmonic series, but for competitive programming purposes, you can consider this to be very close to linear, i.e., almostO(N), becauselog log Ngrows extremely slowly. -
Space Complexity: The algorithm requires a boolean array of size
N+1, so the space complexity isO(N).
As a C++ programmer aiming for high performance, there are a couple more practical details worth knowing.
-
vector<bool>vs.vector<char>: C++'svector<bool>is a specialized template that packs booleans into bits to save space (using 8x less memory thanvector<char>). While this is memory-efficient, accessing individual bits can be slower than accessing full bytes. For the Sieve, however, where memory access patterns are the bottleneck,vector<bool>is often faster. -
Array Declaration Limits: In C++, arrays declared inside a function (local scope, on the stack) have a strict size limit, typically around a few megabytes. An array like
bool is_prime[10000000]would cause a stack overflow. To use large arrays, you must declare them globally (outside of any function), where the memory is allocated differently and the limit is much higher.
The following resources elaborate on these points.
This section discusses the performance trade-offs between vector<bool> and vector<char> for Sieve implementations.
Read the section on memory and speed to understand the practical performance implications of your data structure choice.
L2 | Sieve of Eratosthenes | Raj (Striver) | Prime Numbers for CP
This final segment of the video covers the crucial topic of memory limits for arrays in C++, a common pitfall for competitive programmers.
Watch the discussion from the constraints on array declaration size, explaining the difference between local and global declarations.
Summary and Next Steps
You've now added a powerful pre-computation algorithm to your arsenal. Here are the key takeaways:
- The Sieve of Eratosthenes is used to find all primes up to
Nefficiently, which is ideal for problems with multiple queries. - The core idea is to iteratively mark the multiples of prime numbers as composite.
- Key optimizations are essential: the outer loop runs up to
sqrt(N), and the inner marking loop starts fromi*i. - The time complexity is
O(N log log N), which is very fast in practice. - The space complexity is
O(N). - For C++ implementation, be mindful of practical details like using
vector<bool>and declaring large arrays globally.
In our previous lesson, we learned to test a single number for primality. Today, we learned to find all primes up to N. In the next lesson, we will combine these ideas to tackle another fundamental problem: finding the prime factorization of a number. The Sieve can even be adapted to make this process faster, a connection we will explore soon.