Skip to content
DSA math bits 8 min read

Prime Numbers & the Sieve of Eratosthenes

A prime number is an integer greater than 1 whose only divisors are 1 and itself (2, 3, 5, 7, 11, …). Two tasks dominate prime problems: testing one number for primality (do it in O(√n)) and listing every prime up to n with the Sieve of Eratosthenes (in O(n log log n)). This page covers both, plus the trial-division factorization that builds on them.

Testing a single number — trial division to √n

If n has a divisor larger than √n, it must also have one smaller than √n, so you only need to check up to the square root. Skip even numbers after 2 to halve the work.

bool isPrime(long long n) {
    if (n < 2) return false;
    if (n < 4) return true;            // 2 and 3
    if (n % 2 == 0) return false;
    for (long long i = 3; i * i <= n; i += 2)
        if (n % i == 0) return false;
    return true;
}

Pitfall: Use the condition i * i <= n, not i <= sqrt(n). Floating-point sqrt can round wrong near perfect squares and misclassify a number. In C++/Java, ensure i * i itself doesn’t overflow — use a 64-bit type when n can be large.

Finding all primes up to n — the Sieve of Eratosthenes

To list every prime up to n, don’t test each number separately. Instead, start assuming all are prime, then repeatedly cross out the multiples of each prime you find. Whatever survives is prime.

#include <vector>
std::vector<int> sieve(int n) {
    std::vector<bool> isComposite(n + 1, false);
    std::vector<int> primes;
    for (int i = 2; i <= n; i++) {
        if (!isComposite[i]) {
            primes.push_back(i);
            for (long long j = (long long)i * i; j <= n; j += i)
                isComposite[j] = true;
        }
    }
    return primes;
}

Going deeper: Starting the inner loop at i * i (not 2 * i) is the key optimization — every smaller multiple of i was already crossed out by a smaller prime. This is what gives the O(n log log n) runtime, which is nearly linear in practice.

Prime factorization

Once you can test or sieve primes, factoring is trial division that divides out each factor as you find it. The leftover (if greater than 1) is a single large prime factor.

#include <vector>
std::vector<long long> factorize(long long n) {
    std::vector<long long> factors;
    for (long long d = 2; d * d <= n; d++)
        while (n % d == 0) { factors.push_back(d); n /= d; }
    if (n > 1) factors.push_back(n); // leftover prime
    return factors;
}

Complexity

TaskTimeSpace
isPrime(n)O(√n)O(1)
Sieve up to nO(n log log n)O(n)
Factorize nO(√n)O(log n) factors

When to use which

Use trial division to test a handful of numbers or factor one value. Use the sieve when you’ll query primality many times up to a fixed bound — precompute once, answer in O(1). Both lean on the GCD ideas and feed into modular arithmetic. Next: modular arithmetic and fast exponentiation.

Last updated June 25, 2026
Was this helpful?