What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Use trial division: return False for integers 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.

The standard Python solution

Python’s math.isqrt() returns the floor of the exact square root for a nonnegative integer and has been available since Python 3.8. Using it avoids floating-point rounding when choosing the loop boundary. See the Python 3.13.5 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 important. Python’s range excludes its stop value, so without it an exact square such as 49 would never test divisor 7. This function expects an integer. The initial guard also ensures that a negative value is never passed to isqrt, whose argument must be nonnegative.

Why checking only through the square root works

A prime is an integer greater than 1 with no positive divisors other than 1 and itself. For a composite number, factors occur in pairs. If both factors were greater than its square root, their product would be greater than the number. Therefore, every composite integer has at least one factor less than or equal to its square root. Testing that range is sufficient; any larger factor would have a matching smaller factor that the loop would already find. The same factor-pair reasoning is described in this trial-division guide.

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

What the function returns for boundary values

Input Result Reason
Negative integer False Prime numbers are positive integers greater than 1.
0 False It is below 2.
1 False It has only one positive divisor, so it is not prime.
2 True No divisor in the tested range divides it.
Small composite, such as 9 False 3 divides it.
Large prime True No divisor through its integer square root divides it.

Run a complete example

This script reads one value, validates that it is an integer, and prints a clear result.

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


raw = input("Enter an integer: ").strip()
try:
    number = int(raw)
except ValueError:
    print("Please enter a whole number, such as 97 or -4.")
else:
    print(f"{number} is {'prime' if is_prime(number) else 'not prime'}.")

For example, entering 97 prints 97 is prime.; entering 100 prints 100 is not prime. Converting with int accepts ordinary decimal text, including a leading sign, but rejects values such as 3.14.

How the loop behaves

Early exit for composites

The function returns as soon as it finds a divisor. For 100, divisor 2 is enough; it does not continue testing through 10. This usually makes obvious composites inexpensive.

Worst case for a prime

A prime has no early divisor, so every candidate from 2 through isqrt(n) is tested. The number of modulus operations grows on the order of the square root of n. No performance crossover point for another algorithm is established by the available sources, so measure your actual workload before choosing a more complex method.

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.

Why math.sqrt is not the preferred boundary

math.sqrt produces a floating-point value. For sufficiently large integers, converting an inexact float to a loop limit can produce a boundary error. math.isqrt computes an exact integer floor and is the standard-library operation intended for this job. Its behavior and Python 3.8 introduction are documented in the Python 3.11 math documentation and the current documentation linked above.

Small optimization: skip even candidates

After checking 2, every even number can be skipped because an even input greater than 2 is composite. This reduces the number of modulus operations while preserving the same result.

from math import isqrt

def is_prime_skip_evens(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

The straightforward version is often easier to read and is fast enough for occasional checks. Use this variant when profiling shows that repeated single-number checks justify the extra branch; it is not a different primality rule.

Checking many numbers: use a sieve when the limit is known

Running trial division independently for every value repeats the same work. If you need primality results for all integers up to a known maximum, a Sieve of Eratosthenes marks composites once and then answers membership checks in constant time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
def primes_up_to(limit: int) -> list[int]:
    if limit < 2:
        return []

    prime = bytearray(b"x01") * (limit + 1)
    prime[0:2] = b"x00x00"

    p = 2
    while p * p <= limit:
        if prime[p]:
            prime[p * p : limit + 1 : p] = b"x00" * (((limit - p * p) // p) + 1)
        p += 1

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


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

A sieve uses memory proportional to the upper limit, so it is a poor fit when the maximum is extremely large or when you only need one isolated value. The available guidance recommends choosing between trial division and a sieve based on input count and whether a fixed bound is known; it does not establish a universal size at which one always wins.

Choosing an approach

Situation Recommended approach Trade-off
One number or occasional checks Basic is_prime Shortest and clearest implementation; up to square-root candidates.
Many isolated checks Odd-candidate variant, after profiling Fewer tests, with slightly more branching and code.
All values through a known maximum Sieve of Eratosthenes Reuses work and answers lookups quickly; allocates memory for the range.
Cryptographic-size or security-critical inputs Use a cryptography-focused, reviewed library and its documented primality test This simple educational function provides no cryptographic assurance, and the available evidence does not identify a particular library or threshold.

Testing the implementation

Use a table of known values, including boundaries, small factors, squares, and values near the square-root cutoff.

def test_is_prime() -> None:
    expected = {
        -10: False,
        0: False,
        1: False,
        2: True,
        3: True,
        4: False,
        9: False,
        25: False,
        29: True,
        49: False,
        97: True,
    }

    for number, answer in expected.items():
        assert is_prime(number) is answer, number


test_is_prime()
print("All tests passed")

Useful properties to test

  • Every input below 2 returns False.
  • Every returned True value is greater than 1.
  • If a number is reported composite, dividing by at least one integer from 2 through its square root has remainder zero.
  • Perfect squares such as 4, 9, 25, and 49 are rejected, confirming that the inclusive isqrt(n) boundary is tested.

Troubleshooting common mistakes

Returning true for 0 or 1

Starting the loop without an n < 2 guard leaves the range empty for these values, so the function may incorrectly return True. Reject them before the loop.

Missing the square-root endpoint

range(2, isqrt(n)) excludes the endpoint. Use range(2, isqrt(n) + 1); otherwise perfect squares can be misclassified.

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

Calling isqrt with a negative value

math.isqrt requires a nonnegative integer. Keep the below-2 guard before the call, or validate input separately if your API accepts other numeric types.

Passing floats

A value such as 7.0 is not an integer input for this function. Decide at the boundary whether to reject floats, accept only values whose integer conversion is exact, or require callers to supply int values. Do not silently truncate a fractional value.

Expecting cryptographic guarantees

Trial division is suitable for learning, ordinary validation, and moderate integers. The available sources do not establish a cryptographic algorithm, security guarantee, or input-size threshold. For key-generation or protocol code, follow the documentation of a dedicated, audited cryptographic package rather than adapting this example.

Or skip the browser setup

If your development workflow also needs clean screenshots of documentation, test pages, or demos, ScreenshotNeo is a website screenshot API and MCP server. It is separate from the Python primality function, but can automate visual checks without configuring a local browser. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports the page verdict and billing status in X-Page-Verdict and X-Billed headers.

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

One GET request returns PNG, JPEG, WebP, or a PDF. The API supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets and custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs.

Use the documented endpoint and options at ScreenshotNeo’s API documentation:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

For Python, the equivalent request is:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.

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

Frequently asked questions

Does this function accept arbitrary Python numeric objects?

No. It is typed and written for integers. Enforce that contract at your input boundary instead of relying on implicit conversion.

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

Is a sieve always faster?

No. A sieve trades memory and setup work for reuse. It is appropriate when many values share a known upper limit; isolated checks generally need only trial division.

Which Python version is required?

The shown implementation requires Python 3.8 or newer because it uses math.isqrt. On older versions, you would need a different exact-integer square-root implementation, but upgrading is the clearer option when possible.

Frequently Asked Questions

Does this function accept arbitrary Python numeric objects?

No. It is intended for integers; validate that contract at your input boundary.

Is a sieve always faster?

No. A sieve uses memory and setup work to reuse results, so it is most useful for many values below a known limit.

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

Which Python version is required?

The code uses math.isqrt, which was added in Python 3.8.

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.