Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 18 – Maths for DSA

Primes: trial division, sieves and factorisation

Test whether a number is prime by trial division, list primes with the sieve of Eratosthenes, and factorise many numbers with a smallest-prime-factor table.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: Trial division up to the square root · The sieve of Eratosthenes · Smallest prime factors: factorising many numbers · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Test primality by trial division up to the integer square root
  • Generate every prime up to n with the sieve of Eratosthenes
  • Decompose many numbers into primes with a smallest-prime-factor sieve
  • Choose between trial division, a sieve and a factor table for a workload

Before you start

A prime is a whole number greater than 1 whose only divisors are 1 and itself: 2, 3, 5, 7, 11 and so on. Three tools cover almost every prime question in DSA. To test one number n, trial division tries divisors up to √n, which costs O(√n), because a divisor larger than √n always comes paired with one smaller than √n. To list every prime up to n, the sieve of Eratosthenes crosses out the multiples of each prime, starting at its square, in O(n log log n) time and O(n) memory. To factorise many numbers up to n, a smallest-prime-factor sieve stores each number’s smallest prime factor, and any number then splits into primes with one lookup per factor, O(log n) each.

Trial division up to the square root

If n = d × e with d ≤ e, then d × d ≤ n, so d ≤ √n: every composite number has a divisor no larger than its square root. Trial division can therefore stop at √n, and after 2 it needs to try only odd divisors, about √n / 2 divisions for a prime:

Trial division with the divisions counted Python · trial/trial_division.py
import math


def is_prime(n):
    """Trial division by 2 and by odd numbers up to the integer square root. Returns (answer, divisions made)."""
    if n < 2:
        return False, 0
    if n % 2 == 0:
        return n == 2, 1
    divisions = 1
    for d in range(3, math.isqrt(n) + 1, 2):
        divisions += 1
        if n % d == 0:
            return False, divisions
    return True, divisions


for n in [97, 91, 1_000_003, 999_999_937, 10**12 + 39, 10**12 + 41]:
    prime, divisions = is_prime(n)
    print(f"{n:>17,}  {'prime' if prime else 'composite':<9}  {divisions:>7,} divisions")

print()
print("bound for 10^16 - 1:      int(math.sqrt(n)) =", int(math.sqrt(10**16 - 1)), " math.isqrt(n) =", math.isqrt(10**16 - 1))
print("bound for (2^53 + 1)^2:   int(math.sqrt(n)) =", int(math.sqrt((2**53 + 1) ** 2)), " math.isqrt(n) =", math.isqrt((2**53 + 1) ** 2))

Output

               97  prime            5 divisions
               91  composite        4 divisions
        1,000,003  prime          500 divisions
      999,999,937  prime       15,811 divisions
1,000,000,000,039  prime      500,000 divisions
1,000,000,000,041  composite        2 divisions

bound for 10^16 - 1:      int(math.sqrt(n)) = 100000000  math.isqrt(n) = 99999999
bound for (2^53 + 1)^2:   int(math.sqrt(n)) = 9007199254740992  math.isqrt(n) = 9007199254740993

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 trial_division.py

A prime near 10¹² takes 500,000 divisions, while most composites stop early: 10¹² + 41 is divisible by 3, so the second division settles it. The bound itself has to be exact. int(math.sqrt(n)) turns n into a float first, which gave 100,000,000 for 10¹⁶ − 1, one too large, and 2⁵³ for (2⁵³ + 1)², one too small: for the square of a prime that large, the loop would stop one short of the only divisor it has to try. math.isqrt returns the exact integer square root of any whole number.

The sieve of Eratosthenes

To list the primes up to n, mark every number from 2 to n as possibly prime. The smallest number still marked, p, is prime, since no smaller prime divides it. Cross out its multiples from p² on: each smaller multiple k × p with k < p has a prime factor smaller than p and is already crossed out. Move to the next marked number, and stop once p² is larger than n; every number still marked is prime.

The numbers 1 to 60 after the sieve: 17 primes boxed in bold, every other number tagged with the prime that crossed it out.123425627829310211122131421531621718219202213222232422552622732822930231322333342355362373823934024142243442453462474824975025135225354255556257358259602

The sieve of Eratosthenes on 1 to 60, after crossing out the multiples of 2, 3, 5 and 7

Text description of the diagram

The numbers 1 to 60 are laid out in six rows of ten. The sieve needs only the primes up to 7 here, because 7 is the largest prime whose square, 49, is at most 60.

  • The 17 primes, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 and 59, have a white box with a bold border. Nothing crossed them out.
  • Every other number from 4 to 60 is shaded and carries a small tag in its top right corner: the prime that crossed it out first, which is its smallest prime factor. Even numbers carry 2; 9, 15, 21, 27, 33, 39, 45, 51 and 57 carry 3; 25, 35 and 55 carry 5; 49 carries 7.
  • The first number that each prime crosses out has a dashed border: 4 for 2, 9 for 3, 25 for 5 and 49 for 7. Every smaller multiple of the prime, such as 15 for 5, was already crossed out by a smaller prime, which is why the crossing starts at the prime's square.
  • The number 1 is grey: it is neither prime nor composite.

This program sieves up to 10⁷ with a bytearray, one byte a number, and counts how many times a number is crossed out:

The sieve up to 10⁷, with its crossings counted Python · sieve/sieve.py
import math


def sieve(n):
    """is_prime[i] is 1 exactly when i is prime, for 0 <= i <= n (the sieve of Eratosthenes)."""
    is_prime = bytearray([1]) * (n + 1)
    is_prime[0:2] = b"\x00\x00"
    for p in range(2, math.isqrt(n) + 1):
        if is_prime[p]:
            # p is prime: cross out p*p, p*p + p, ..., up to n
            is_prime[p * p :: p] = bytes(len(range(p * p, n + 1, p)))
    return is_prime


is_prime = sieve(100)
print("primes up to 100:", [i for i in range(101) if is_prime[i]])

is_prime = sieve(10**7)
print(f"primes up to 10^7: {sum(is_prime):,}")
print()
print("         n   crossings   crossings / n   ln ln n")
for n in (10**4, 10**5, 10**6, 10**7):
    crossings = sum((n - p * p) // p + 1 for p in range(2, math.isqrt(n) + 1) if is_prime[p])
    print(f"{n:>10,}  {crossings:>10,}  {crossings / n:>14.3f}  {math.log(math.log(n)):>8.3f}")

Output

primes up to 100: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
primes up to 10^7: 664,579

         n   crossings   crossings / n   ln ln n
    10,000      16,981           1.698     2.220
   100,000     193,078           1.931     2.443
 1,000,000   2,122,048           2.122     2.626
10,000,000  22,850,051           2.285     2.780

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sieve.py

It finds the 664,579 primes up to 10⁷. While n grows a thousandfold, the crossings per number grow only from 1.70 to 2.29, in step with ln ln n, which is where the O(n log log n) comes from. The memory is one byte a number, 10 MB for 10⁷.

Smallest prime factors: factorising many numbers

A sieve can record more than yes or no. Keep for every number x its smallest prime factor, spf[x], set by the first prime that crosses x out. Then x splits into primes by repeated division: spf[x] is one factor, and x / spf[x] holds the rest. A number up to n has at most log₂ n prime factors, so each factorisation takes O(log n) lookups:

A smallest-prime-factor table up to 10⁶, against trial division Python · spf/spf.py
import math


def smallest_prime_factors(n):
    """spf[i] is the smallest prime that divides i, for 2 <= i <= n."""
    spf = list(range(n + 1))
    for p in range(2, math.isqrt(n) + 1):
        if spf[p] == p:  # nothing smaller divides p, so p is prime
            for m in range(p * p, n + 1, p):
                if spf[m] == m:
                    spf[m] = p
    return spf


def factorise(x, spf):
    """The prime factors of x, smallest first, with one table lookup per factor."""
    factors = []
    while x > 1:
        factors.append(spf[x])
        x //= spf[x]
    return factors


def trial_factorise(x):
    """The same factors by trial division, and the number of divisions it made."""
    factors, divisions, d = [], 0, 2
    while d * d <= x:
        divisions += 1
        if x % d == 0:
            factors.append(d)
            x //= d
        else:
            d += 1
    if x > 1:
        factors.append(x)
    return factors, divisions


N = 1_000_000
spf = smallest_prime_factors(N)
for x in [360, 999_983, 524_288, 735_134, 997_002]:
    print(f"{x:>7,} = {' × '.join(map(str, factorise(x, spf)))}")

lookups = divisions = 0
for x in range(2, 100_001):
    by_table = factorise(x, spf)
    by_trial, made = trial_factorise(x)
    assert by_table == by_trial
    lookups += len(by_table)
    divisions += made
print(f"factorising every number from 2 to 100,000: {lookups:,} table lookups, {divisions:,} trial divisions")

Output

    360 = 2 × 2 × 2 × 3 × 3 × 5
999,983 = 999983
524,288 = 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 × 2
735,134 = 2 × 31 × 71 × 167
997,002 = 2 × 3 × 3 × 3 × 37 × 499
factorising every number from 2 to 100,000: 343,614 table lookups, 6,956,686 trial divisions

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 spf.py

The most lookups below 10⁶ go to 524,288 = 2¹⁹, nineteen of them. Factorising every number from 2 to 100,000 took 343,614 lookups with the table and 6,956,686 trial divisions, about twenty times as many.

Choosing a method

Three prime tools for numbers up to n
Criterion Trial divisionSieve of EratosthenesSmallest-prime-factor sieve
Answers Is this one number prime?Which numbers up to n are prime?What are the prime factors of x ≤ n?
Work O(√n) per numberO(n log log n) once, then O(1) per questionAbout n log log n once, then O(log n) per number
Memory NoneOne byte (or one bit) per numberOne integer per number
When to choose A few numbersMany primality questions up to nMany factorisations up to n

Trial division needs no memory and suits a handful of questions. The sieve answers a primality question in one lookup after one pass, and the factor table answers factorisation questions the same way. Three further tools reach where these stop. A segmented sieve finds the primes in a far window, such as 10¹⁴ to 10¹⁴ + 10⁶, with memory for the window only. The Miller–Rabin test decides whether one large number is prime with a few modular powers. A linear sieve crosses out every composite exactly once.

Exercise and self-check

Count the primes in up to 100,000 ranges of numbers up to 10⁷, sieving only once.

Exercise · Medium · Python, JavaScript

Count the primes in many ranges

Write count_primes(queries) (JavaScript: countPrimes). queries is a list of pairs (l, r) (JavaScript: [l, r]) with 1 <= l <= r <= 10⁷, and the function returns one number per pair, in the same order: how many primes p satisfy l <= p <= r.

There can be 100,000 pairs, so testing every number of every range is far too slow. Sieve once, up to the largest r, and answer each pair from counts you prepare beforehand.

The sample tests check single numbers, calls whose largest range ends at 1 or 2, ranges that start at 1 or 2, ranges with no prime, a few hundred random ranges against trial division, the count up to 10⁶, and 10,000 ranges below 10⁶.

Python · Starter code · ranges.py

def count_primes(queries):
    """For each (l, r), the number of primes p with l <= p <= r."""
    raise NotImplementedError("write count_primes")
The sample tests · test_ranges.py
import random

from ranges import count_primes


def slow_is_prime(n):
    """Trial division: right, and too slow for large ranges."""
    if n < 2:
        return False
    d = 2
    while d * d <= n:
        if n % d == 0:
            return False
        d += 1
    return True


def test_single_numbers():
    """one-number ranges: 1 is not prime, 2 and 97 are, 91 is not"""
    assert count_primes([(1, 1), (2, 2), (97, 97), (91, 91)]) == [0, 1, 1, 0]


def test_tiny_inputs():
    """a call whose largest r is 1 or 2 sieves only that far"""
    assert count_primes([(1, 1)]) == [0]
    assert count_primes([(2, 2)]) == [1]
    assert count_primes([(1, 2), (1, 1)]) == [1, 0]


def test_small_ranges():
    """ranges that start at 1 or 2, and a range without primes"""
    assert count_primes([(1, 10), (2, 10), (1, 100), (90, 100), (114, 126)]) == [4, 4, 25, 1, 0]


def test_order_is_kept():
    """the answers come back in the order of the queries"""
    assert count_primes([(1, 100), (1, 10), (11, 20)]) == [25, 4, 4]


def test_random_against_trial_division():
    """300 random ranges below 3,000, compared with trial division"""
    rng = random.Random(9)
    queries = []
    for _ in range(300):
        l = rng.randint(1, 3000)
        queries.append((l, rng.randint(l, 3000)))
    expected = [sum(1 for x in range(l, r + 1) if slow_is_prime(x)) for l, r in queries]
    assert count_primes(queries) == expected


def test_up_to_a_million():
    """78,498 primes up to 10^6, and the ones in its last thousand numbers"""
    assert count_primes([(1, 10**6), (999_001, 10**6)]) == [78_498, 65]


def test_ten_thousand_ranges():
    """10,000 ranges below 10^6: their answers add up to a known total"""
    rng = random.Random(4)
    queries = []
    for _ in range(10_000):
        l = rng.randint(1, 10**6)
        queries.append((l, min(10**6, l + rng.randint(0, 5_000))))
    answers = count_primes(queries)
    assert len(answers) == 10_000
    assert sum(answers) == 1_977_492

JavaScript · Starter code · ranges.mjs

/** For each [l, r], the number of primes p with l <= p <= r. */
export function countPrimes(queries) {
  throw new Error('write countPrimes');
}
The sample tests · ranges.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { countPrimes } from './ranges.mjs';

/** Trial division: right, and too slow for large ranges. */
function slowIsPrime(n) {
  if (n < 2) return false;
  for (let d = 2; d * d <= n; d++) if (n % d === 0) return false;
  return true;
}

/** A small seeded generator (Park and Miller), so every run builds the same ranges. */
function generator(seed) {
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  return (k) => Math.floor(random() * k);
}

test('one-number ranges: 1 is not prime, 2 and 97 are, 91 is not', () => {
  assert.deepEqual(countPrimes([[1, 1], [2, 2], [97, 97], [91, 91]]), [0, 1, 1, 0]);
});

test('a call whose largest r is 1 or 2 sieves only that far', () => {
  assert.deepEqual(countPrimes([[1, 1]]), [0]);
  assert.deepEqual(countPrimes([[2, 2]]), [1]);
  assert.deepEqual(countPrimes([[1, 2], [1, 1]]), [1, 0]);
});

test('ranges that start at 1 or 2, and a range without primes', () => {
  assert.deepEqual(countPrimes([[1, 10], [2, 10], [1, 100], [90, 100], [114, 126]]), [4, 4, 25, 1, 0]);
});

test('the answers come back in the order of the queries', () => {
  assert.deepEqual(countPrimes([[1, 100], [1, 10], [11, 20]]), [25, 4, 4]);
});

test('300 random ranges below 3,000, compared with trial division', () => {
  const pick = generator(9);
  const queries = [];
  for (let i = 0; i < 300; i++) {
    const l = 1 + pick(3000);
    queries.push([l, l + pick(3001 - l)]);
  }
  const expected = queries.map(([l, r]) => {
    let c = 0;
    for (let x = l; x <= r; x++) if (slowIsPrime(x)) c++;
    return c;
  });
  assert.deepEqual(countPrimes(queries), expected);
});

test("78,498 primes up to 10^6, and the ones in its last thousand numbers", () => {
  assert.deepEqual(countPrimes([[1, 1000000], [999001, 1000000]]), [78498, 65]);
});

test('10,000 ranges below 10^6: their answers add up to a known total', () => {
  const pick = generator(4);
  const queries = [];
  for (let i = 0; i < 10000; i++) {
    const l = 1 + pick(1000000);
    queries.push([l, Math.min(1000000, l + pick(5001))]);
  }
  const answers = countPrimes(queries);
  assert.equal(answers.length, 10000);
  assert.equal(answers.reduce((s, x) => s + x, 0), 1963500);
});
A hint

After the sieve, build count[i], the number of primes up to i: count[i] = count[i - 1] + is_prime[i]. The answer for (l, r) is then count[r] - count[l - 1], one subtraction per pair.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Prime Number Checker & Generator Check whether a number of your own is prime, or test the answers of your sieve.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 10,007 is prime. How many divisions does the lesson's trial division make on it, testing 2 and then the odd numbers up to math.isqrt(10007)?

    Type a number.

    Show the answer to question 1

    Answer: 50 divisions

    math.isqrt(10007) is 100, because 100² = 10,000 ≤ 10,007 < 101². The function divides by 2 once and then by the 49 odd numbers from 3 to 99, and none of them divides 10,007: 1 + 49 = 50 divisions.

  2. Question 2 of 5 Why may the sieve start crossing out the multiples of a prime p at p², not at 2p?

    Choose one answer.

    Show the answer to question 2

    Answer: Every multiple k × p with k < p has a prime factor smaller than p, so it is already crossed out

    A multiple k × p with 2 ≤ k < p has a prime factor of k, which is smaller than p, and that prime came first in the loop and crossed k × p out. The first multiple that no smaller prime can reach is p × p.

  3. Question 3 of 5 To sieve every number up to 1,000, how many primes must cross out their multiples?

    Type a number.

    Show the answer to question 3

    Answer: 11 primes

    Only primes p with p² ≤ 1,000 cross anything out, so p ≤ 31, because 31² = 961 and 37² = 1,369. The primes up to 31 are 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 and 31: eleven of them.

  4. Question 4 of 5 A service answers a million questions "is n prime?" with every n at most 10⁷. Which design fits best?

    Choose one answer.

    Show the answer to question 4

    Answer: Sieve once up to 10⁷, then answer each question with one lookup

    The sieve costs about 10⁷ bytes and a fraction of a second once, and then each question is one array lookup. Trial division would make up to about 1,600 divisions per question for n near 10⁷, and a factor table answers a harder question than the one asked, with four times the memory in a compiled language.

  5. Question 5 of 5 With a smallest-prime-factor table, how many lookups does it take to factorise 524,288 = 2¹⁹?

    Type a number.

    Show the answer to question 5

    Answer: 19 lookups

    Every lookup finds the factor 2 and halves the number: 524,288, 262,144, … down to 1. Nineteen prime factors take nineteen lookups, the most for any number up to 10⁶.

Why the sieve is right and what it costs, proved and counted Pro: opens with a Pro, Premium or Ultimate pass

The sieve needs two arguments: that every number left marked is prime, and that its work grows like n log log n. This part proves both, compares the counted crossings with the prediction up to 10⁹, measures four ways to store the marks and counts a sieve that crosses out every composite once.

In this part:

  • Every number left marked is prime: the invariant, proved
  • n log log n from the sum of 1/p, checked against the count
  • One byte, one bit or half a bit per number, measured
  • A linear sieve: every composite crossed out once

3 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Primes in a far window, and numbers too large to sieve Pro: opens with a Pro, Premium or Ultimate pass

A sieve from 1 cannot reach 10¹⁴, and no sieve can test one 64-bit number. This part sieves a window near 10¹⁴ with the primes up to its square root, shows which composites fool Fermat’s test, and makes Miller–Rabin exact for every 64-bit number with twelve fixed bases.

In this part:

  • A segmented sieve for 10¹⁴ to 10¹⁴ + 10⁶
  • Fermat’s test, and the numbers that fool it
  • Miller–Rabin: a stronger test, exact below 2⁶⁴ with twelve bases

2 runnable examples (Python) · about 3 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

The factor table in five languages, the 32-bit trap and a practice set Pro: opens with a Pro, Premium or Ultimate pass

The smallest-prime-factor table is the same array in every language; the traps are in the integer types around it. This part runs one program in Python, JavaScript, Java, C++ and Go, reproduces a 32-bit overflow that makes a famous prime look composite, and ends with a practice set.

In this part:

  • One factor table in five languages, with run records
  • The 32-bit trap reproduced: i × i wraps and a prime fails
  • Edge cases worth a test
  • Practice set

2 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher · Warm-up

    Why does trial division only need to try divisors up to √n, and how many divisions does it make on a prime near 10¹²?

    If n = d × e and both d and e were larger than √n, their product would be larger than n, so a composite number always has a divisor no larger than √n. When no number from 2 up to the integer square root divides n, no larger number can either, and n is prime. Testing 2 and then only the odd divisors, a prime near 10¹² needs about √n / 2 = 500,000 divisions; the run on 10¹² + 39 made exactly that many. Compute the bound with an exact integer square root, such as math.isqrt in Python: a float square root of a number above 2⁵³ can come out one too small and stop just before the divisor of a prime's square.

  2. Mid to senior

    Service A answers a million questions a day of the form "is n prime?" with n at most 10⁷. Service B checks single 64-bit numbers that users type in. What do you build for each, with memory and time, and what breaks if you reuse A's design for B?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid

    You must factorise a million numbers, each at most 10⁷, within a second. Compare trial division with a smallest-prime-factor table using operation counts, give the table's memory in Python and in a compiled language, and say when the table is the wrong choice.

    Model answer, Pro. First step and follow-ups

  4. Senior

    Count the primes between 10¹² and 10¹² + 10⁷. Why not sieve from 1, how does a segmented sieve work, how much memory and time does it need, and how would you check the count?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Trial division stops at the integer square root: a composite always has a divisor no larger than √n. Compute the bound with math.isqrt, never through a float.
  • The sieve of Eratosthenes crosses out the multiples of each prime from p² and stops at √n: O(n log log n) time and one byte (or one bit) per number.
  • A smallest-prime-factor table factorises any number up to n with one lookup per prime factor, at most log₂ n.
  • Choose by the workload: a few numbers, trial division; many primality questions, a sieve; many factorisations, a factor table.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.