Quick answer: To check whether a number is prime, reject values below two, then test divisors from two through math.isqrt(n). If a divisor divides evenly, the number is composite; if none does, it is prime. Checking only through the square root is sufficient because every factor pair has at least one factor no larger than the square root. For many values up to a fixed limit, use a sieve instead.

A prime number is an integer greater than 1 with exactly two positive divisors: 1 and itself. In Python, the usual beginner-friendly approach is trial division: try possible divisors and stop as soon as one divides evenly. Primality asks whether a number has a divisor, while Prime Factorization in Python Guide records every prime divisor and its multiplicity.
You do not need to test every number up to n - 1. If a number has a factor larger than its square root, it also has a matching factor smaller than its square root. That means checking divisors up to math.isqrt(n) is enough.
The official Python documentation for math.isqrt() is useful for the square-root limit.
Start with clear rules: 0 and 1 are not prime, negative numbers are not prime, 2 is prime, and any even number larger than 2 is not prime. Handling those cases early makes the main loop smaller and easier to read.
For small classroom examples, a simple loop is fine. For cryptography or very large integers, use a library designed for number theory instead of a basic trial-division function.
Prime checking is a good example of improving an algorithm without making it hard to read. The first version can test every possible divisor. The improved version stops at the square root. The faster version skips even divisors after handling 2. Each step keeps the same result while reducing unnecessary work.
Keep the input type clear. These functions expect integers. If a value comes from a form, command line, file, or API response, parse it first and reject invalid text before calling the prime checker.
Also decide what your program should do with non-positive values. In most math and programming contexts, values below 2 simply return False. Raising an exception is usually only needed when negative input means the caller made a mistake.
The examples below keep the logic explicit so you can see each decision.
Check A Number With Trial Division
This direct version checks every possible divisor from 2 up to one less than the number.
number = 17
is_prime = number > 1
for divisor in range(2, number):
if number % divisor == 0:
is_prime = False
break
print(is_prime)
The loop stops early if it finds a divisor. For 17, no divisor is found, so the result is True.
This version is easy to understand, but it does extra work for larger numbers.
Use this form when teaching the idea of divisibility. For real checks, move the logic into a function so the rules can be reused and tested.
Handle Small And Negative Numbers
Numbers less than 2 are not prime.
def is_prime_basic(number):
if number < 2:
return False
for divisor in range(2, number):
if number % divisor == 0:
return False
return True
print(is_prime_basic(-7))
print(is_prime_basic(1))
print(is_prime_basic(2))
This function returns early for values that cannot be prime. That makes the later loop responsible only for realistic candidates.
Early returns also make the function easier to audit. Each special case is handled once, and the main loop stays focused on divisibility.

Use A Square-Root Limit
math.isqrt() returns the integer square root without floating-point rounding issues.
from math import isqrt
def is_prime(number):
if number < 2:
return False
for divisor in range(2, isqrt(number) + 1):
if number % divisor == 0:
return False
return True
print(is_prime(97))
print(is_prime(100))
This is much faster than checking all divisors up to the number for larger inputs.
The + 1 matters because range() stops before the end value.
For example, if the square root limit is 7, the loop must include 7 as a possible divisor. Without the extra one, that final divisor would be skipped.
Skip Even Divisors
After handling 2, every other even number can be rejected or skipped.
from math import isqrt
def is_prime_fast(number):
if number < 2:
return False
if number == 2:
return True
if number % 2 == 0:
return False
for divisor in range(3, isqrt(number) + 1, 2):
if number % divisor == 0:
return False
return True
print(is_prime_fast(29))
print(is_prime_fast(91))
This loop checks only odd divisors after rejecting even candidates. It is still simple, but it cuts the loop work roughly in half.
This version is a good practical default for ordinary scripts. It avoids unnecessary even checks without adding complex number-theory code.

List Primes In A Range
Reuse the prime-checking function to collect primes from a range.
from math import isqrt
def is_prime(number):
if number < 2:
return False
for divisor in range(2, isqrt(number) + 1):
if number % divisor == 0:
return False
return True
primes = [number for number in range(2, 20) if is_prime(number)]
print(primes)
This style is useful for small reports and learning exercises. For very large ranges, look into sieve algorithms.
A sieve is better when you need many primes up to a limit because it reuses work across the whole range. Calling a single-number test repeatedly is simpler, but not always the fastest approach.
Validate User Input
When the number comes from text input, parse and validate it before checking primality.
from math import isqrt
def is_prime(number):
if number < 2:
return False
for divisor in range(2, isqrt(number) + 1):
if number % divisor == 0:
return False
return True
text = "31"
number = int(text)
print(is_prime(number))
In a real program, catch ValueError if the input may not be an integer. Keep parsing errors separate from prime-checking logic.
Common mistakes include treating 1 as prime, forgetting to stop at the square-root limit, and using floating-point square roots when integer math is cleaner.
Another common mistake is returning too early from the loop. Only return True after all required divisors have been tested. Returning inside the first non-dividing case can mark composite numbers as prime by accident.
In short, handle values below 2 first, test divisibility only up to isqrt(n), skip even divisors when you want a small speed improvement, and keep user input parsing outside the prime-checking function.

Guard The Small Values
Zero, one, and negative integers are not prime. Keep this rule at the top of the function so the loop only handles candidates for which divisibility makes sense.
def is_prime_basic(number):
if number < 2:
return False
for divisor in range(2, number):
if number % divisor == 0:
return False
return True
print([number for number in range(12) if is_prime_basic(number)])
Stop At The Integer Square Root
math.isqrt returns the exact integer square root without floating-point rounding. Testing through that bound reduces trial divisions from n to approximately the square root of n.
from math import isqrt
def is_prime(number):
if number < 2:
return False
for divisor in range(2, isqrt(number) + 1):
if number % divisor == 0:
return False
return True
for candidate in (2, 3, 9, 17, 49):
print(candidate, is_prime(candidate))

Skip Easy Composite Cases
After handling values below two, two is the only even prime. Checking two first and then testing odd divisors is a simple optimization that preserves the same proof.
from math import isqrt
def is_prime_fast(number):
if number < 2:
return False
if number == 2:
return True
if number % 2 == 0:
return False
return all(number % divisor for divisor in range(3, isqrt(number) + 1, 2))
print(is_prime_fast(97))
Use A Sieve For Many Candidates
A sieve marks multiples of each prime and reuses that work for every value up to the limit. It uses memory proportional to the limit, so choose it for batches of queries rather than one very large isolated integer.
def primes_up_to(limit):
flags = [True] * (limit + 1)
if limit >= 0:
flags[0] = False
if limit >= 1:
flags[1] = False
for candidate in range(2, int(limit ** 0.5) + 1):
if flags[candidate]:
for multiple in range(candidate * candidate, limit + 1, candidate):
flags[multiple] = False
return [number for number, prime in enumerate(flags) if prime]
print(primes_up_to(30))
Python’s official math.isqrt() returns an integer square root, which is the correct bound for trial division. Related references include prime factorization, number predicates, and integer limits.
For related number algorithms, compare prime factorization, integer limits, and iteration patterns when scaling a prime test.
Frequently Asked Questions
What is the simplest prime check in Python?
Handle values below two, then test divisors from two through the integer square root and stop at the first exact division.
Why only check divisors up to the square root?
If a composite number has a factor larger than its square root, its matching factor is smaller than the square root.
Is one a prime number?
No. Prime numbers have exactly two positive divisors, while one has only itself.
How do I test many numbers efficiently?
Use a sieve of Eratosthenes when you need all primes up to a known limit instead of repeating trial division.
Methods 1 through 3 are all the same method. They are only slight variations to be selective about which numbers to iterate. How about adding a method that uses the Sieve of Eratosthenes, and one that uses a lookup table?
Great catch! I’ll update the post with these methods.
Hi,
method 1.5 cannot work and should be updated :p
Cheers
Hi Gabriel,
Thank you for informing me :). I’ve updated the post accordingly.
Regards,
Pratik
I believe your purpose in the statement:
“int(num**1/2)”
is to refer to the root of num, but because you didn’t use parenthesis it becomes:
“int(num/2)”
if you do correct it to:
“int(num**0.5)”
the range needs to be changed in some of your codes here or else the range will be null for lower numbers, I checked for example with 3 and 5.
Correct. Thank you for pointing it out!