Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

For one integer, reject every value below 2, then test divisors from 2 through math.isqrt(n). If any divisor divides evenly, the number is composite; if none does, it is prime. This is exact trial division and needs only Python’s standard library.

The standard-library solution

math.isqrt() returns the floor of an integer’s exact square root. It was added in Python 3.8 and avoids floating-point rounding at the loop boundary; see the Python math documentation.

from math import isqrt

def is_prime(n: int) -> bool:
    if n < 2:
        return False

    for divisor in range(2, isqrt(n) + 1):
        if n % divisor == 0:
            return False

    return True

The + 1 is intentional: Python’s range stops before its second argument. Adding one means an exact square root is tested as a possible divisor.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Example calls

print(is_prime(2))    # True
print(is_prime(17))   # True
print(is_prime(18))   # False
print(is_prime(1))    # False
print(is_prime(-11))  # False

Why checking only to the square root works

A composite number can be written as a product of two positive factors. If both factors were greater than sqrt(n), their product would be greater than n. Therefore, every composite n has at least one factor at or below its square root. Finding no divisor in that interval proves that no non-trivial factor exists.

For example, 91 is composite because 7 × 13 = 91, and 7 is below sqrt(91). Once a divisor is found, the function can return immediately instead of testing the remaining candidates.

Inputs and edge cases

Zero, one, and negative integers

Primality is defined for integers greater than 1. Consequently, 0, 1, and every negative integer return False. The guard runs before isqrt, which requires a nonnegative integer.

Two and other small values

2 is prime. For n == 2, range(2, isqrt(2) + 1) is empty, so the function reaches return True. The same logic handles 3 and other values whose square root is below 2.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Non-integer arguments

The annotation documents the intended input; it does not enforce it at runtime. A float can produce surprising results, and a string raises a type error during comparison or modulo. Validate or convert input at the boundary of your program:

raw = input("Integer: ")
try:
    value = int(raw)
except ValueError:
    print("Enter a whole number.")
else:
    print(is_prime(value))

Python’s bool type is a subclass of int, so is_prime(True) treats True as 1 and returns False. If your API must reject booleans explicitly, add if isinstance(n, bool): raise TypeError(...) before the numeric checks.

How the algorithm behaves

Approach Best fit Work and storage Trade-off
Basic trial division One or occasional integers Tests candidates through isqrt(n); constant extra storage Simple and easy to audit, but repeats work for separate calls
Skip even candidates Single checks where a small optimization is useful Checks 2, then odd divisors only Fewer modulo operations, with slightly more branching
Sieve of Eratosthenes Many queries up to a known maximum Precomputes and stores primality for the whole range More setup and memory, but work is reused across queries

No universal input-count or numeric-size crossover is established; choose according to whether you have one value or a bounded batch, and whether clarity or precomputation matters more.

A small optimization for repeated single checks

After checking 2, every remaining possible prime divisor is odd. This version preserves the same proof while skipping even candidates:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from math import isqrt

def is_prime_odd_only(n: int) -> bool:
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False

    for divisor in range(3, isqrt(n) + 1, 2):
        if n % divisor == 0:
            return False
    return True

Keep the straightforward version when teaching, reviewing, or maintaining code; the optimization is optional rather than a different definition of primality.

Checking many numbers with a sieve

If you need every prime up to a known limit, a sieve marks composites once and lets later lookups run in constant time:

def primes_up_to(limit: int) -> list[int]:
    if limit < 2:
        return []

    prime = [True] * (limit + 1)
    prime[0] = prime[1] = False
    p = 2
    while p * p <= limit:
        if prime[p]:
            for multiple in range(p * p, limit + 1, p):
                prime[multiple] = False
        p += 1

    return [number for number, marked in enumerate(prime) if marked]

known_primes = primes_up_to(100)
print(97 in known_primes)  # True

The sieve is appropriate when the upper bound is known and many values in that interval will be queried. It is unnecessary overhead for a single, very large integer.

Testing the implementation

Use examples that cover boundaries, a small prime, a small composite, and a factor near the square-root limit:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
def test_is_prime():
    assert is_prime(-5) is False
    assert is_prime(0) is False
    assert is_prime(1) is False
    assert is_prime(2) is True
    assert is_prime(3) is True
    assert is_prime(4) is False
    assert is_prime(17) is True
    assert is_prime(25) is False
    assert is_prime(49) is False
    assert is_prime(97) is True

For a command-line script, run these assertions directly with Python; for a project, place them in your test runner’s expected test file. Include perfect squares such as 49 to verify that the isqrt(n) + 1 boundary is not accidentally omitted.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common mistakes and fixes

  • Starting at 1: Every integer is divisible by 1, so it would incorrectly classify all values as composite. Start at 2.
  • Accepting 0 or 1: Add the n < 2 guard before the loop.
  • Stopping at isqrt(n) without the extra one: Python excludes the range stop value. Use range(2, isqrt(n) + 1).
  • Using math.sqrt for the boundary: Floating-point conversion can lose precision for large integers. Prefer exact integer isqrt, as documented by Python.
  • Calling isqrt on a negative number: Reject values below 2 first.
  • Recomputing a sieve for every query: Build it once, retain the resulting table, and answer all bounded-range lookups from it.

Large and security-sensitive numbers

Trial division is transparent and exact, but the number of candidate divisors grows with the square root of the input. The material here does not establish a cryptographic primality algorithm, a security guarantee, or a performance threshold for cryptographic-size integers. Do not substitute this helper for a security review or a proven cryptographic-prime-testing design; select and validate an algorithm appropriate to your threat model and numeric range.

Or skip the browser setup

ScreenshotNeo is unrelated to primality testing, but it can automate clean website captures when a Python workflow also needs a page image. Its API accepts a URL in one request; cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are not billed, and an MCP server lets AI agents take screenshots.

Python example (see the ScreenshotNeo documentation):

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

The equivalent requests are:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo’s free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Frequently Asked Questions

Does the function prove that a number is prime mathematically?

Yes, for an integer input it exhausts every possible divisor from 2 through the exact square-root boundary; the factor-pair argument makes that range sufficient.

Why does the annotation say int if Python does not enforce it?

Annotations document intent for readers and type checkers. Runtime validation is still your responsibility when values come from users, files, or APIs.

Should I cache individual primality results?

Caching can help when the same values recur, but the supplied guidance does not establish a universal cache policy or performance crossover; measure your workload and memory budget.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.