The math.factorial(n) function computes the product of all positive integers less than or equal to non-negative integer n (denoted mathematically as n!), where 0! = 1. Implemented internally in optimized C, it leverages Python's arbitrary-precision integer architecture to compute factorials of arbitrarily large integers without integer overflow.
Combinatorics Foundation: Factorials form the computational backbone for permutations (math.perm(n, k)) and binomial coefficient combinations (math.comb(n, k)), widely applied in statistics, machine learning, and cryptography.
The math.factorial() function takes a non-negative integer and returns its exact factorial:
import math
result = math.factorial(n)
| Parameter | Type | Description |
|---|---|---|
n |
int |
Required. A non-negative integer (n ≥ 0). In Python 3.9+, float values raise ValueError even if integral (e.g. 5.0). |
| Return Value | int |
Returns exact integer n! with unlimited precision. |
ValueError |
Exception | Raised when n < 0 or when n is a float. |
TypeError |
Exception | Raised when n is not an integer-like object. |
In languages like C or Java, standard 64-bit integers overflow at 21!. In Python, integers grow dynamically to consume available memory, allowing calculations of astronomical quantities:
import math
# Small factorials
print("0! =", math.factorial(0)) # 1
print("1! =", math.factorial(1)) # 1
print("5! =", math.factorial(5)) # 120
print("10! =", math.factorial(10)) # 3628800
# Large factorial: 50!
fact_50 = math.factorial(50)
print("\n50! =", fact_50)
print("Number of digits in 50!:", len(str(fact_50)))
# 100! (158 decimal digits)
fact_100 = math.factorial(100)
print("\nNumber of digits in 100!:", len(str(fact_100)))
print("100! begins with:", str(fact_100)[:20], "...")
Output:
0! = 1
1! = 1
5! = 120
10! = 3628800
50! = 30414093201713378043612608166064768844377641568960512000000000000
Number of digits in 50!: 65
Number of digits in 100!: 158
100! begins with: 93326215443944152681 ...
While factorial can be written iteratively or recursively in Python, math.factorial() is significantly faster because it is implemented in C and uses divide-and-conquer binary splitting algorithms:
import math
import sys
# 1. Iterative implementation
def factorial_iterative(n):
res = 1
for i in range(2, n + 1):
res *= i
return res
# 2. Recursive implementation
def factorial_recursive(n):
if n <= 1:
return 1
return n * factorial_recursive(n - 1)
# Verification
n = 20
print("math.factorial: ", math.factorial(n))
print("factorial_iterative:", factorial_iterative(n))
print("factorial_recursive:", factorial_recursive(n))
# Recursion trap: exceeding maximum recursion depth
try:
factorial_recursive(1500)
except RecursionError as e:
print("\nRecursion trap caught:", e)
Output:
math.factorial: 2432902008176640000
factorial_iterative: 2432902008176640000
factorial_recursive: 2432902008176640000
Recursion trap caught: maximum recursion depth exceeded in comparison
sys.getrecursionlimit()). Calling factorial_recursive(1500) triggers a RecursionError. Always use math.factorial() for production systems.
Understanding how math.factorial() handles invalid arguments prevents unexpected runtime crashes:
import math
# 1. Negative numbers raise ValueError
try:
math.factorial(-5)
except ValueError as e:
print("Negative error:", e)
# 2. Float values raise ValueError (Python 3.9+)
try:
math.factorial(5.0)
except ValueError as e:
print("Float error: ", e)
# 3. Non-numeric types raise TypeError
try:
math.factorial("10")
except TypeError as e:
print("String error: ", e)
Output:
Negative error: factorial() not defined for negative values
Float error: factorial() only accepts integral values
String error: 'str' object cannot be interpreted as an integer
Factorials are the mathematical foundation for counting arrangements (order matters) and selections (order does not matter):
| Concept | Formula | Standard Library Function | Description |
|---|---|---|---|
| Permutations | P(n, k) = n! / (n - k)! |
math.perm(n, k) |
Ways to choose and arrange k items from n distinct items. |
| Combinations | C(n, k) = n! / (k! · (n - k)!) |
math.comb(n, k) |
Ways to choose k items from n without regard to order. |
import math
n, k = 10, 3
# Manual factorial formulas
perm_manual = math.factorial(n) // math.factorial(n - k)
comb_manual = math.factorial(n) // (math.factorial(k) * math.factorial(n - k))
# Built-in math functions
perm_native = math.perm(n, k)
comb_native = math.comb(n, k)
print(f"P(10, 3) = {perm_native} (manual: {perm_manual})")
print(f"C(10, 3) = {comb_native} (manual: {comb_manual})")
Output:
P(10, 3) = 720 (manual: 720)
C(10, 3) = 120 (manual: 120)
The probability of getting exactly k successes in n independent Bernoulli trials with probability p is P(X = k) = C(n, k) · pk · (1 - p)n - k:
import math
def binomial_probability(trials, successes, prob_success):
comb = math.comb(trials, successes)
return comb * (prob_success ** successes) * ((1.0 - prob_success) ** (trials - successes))
# Probability of exactly 2 defective parts in a batch of 50 (defect rate = 3%)
prob_2_defects = binomial_probability(trials=50, successes=2, prob_success=0.03)
print(f"Probability of 2 defects in batch of 50: {prob_2_defects:.4%}")
Output:
Probability of 2 defects in batch of 50: 25.5508%
The number of distinct arrangements of a word with duplicate letters is Total! / (count_1! · count_2! · …):
import math
from collections import Counter
def distinct_permutations(word):
counts = Counter(word.upper())
total_length = len(word)
denominator = 1
for count in counts.values():
denominator *= math.factorial(count)
return math.factorial(total_length) // denominator
print("Distinct anagrams of 'BOOK': ", distinct_permutations("BOOK"))
print("Distinct anagrams of 'MISSISSIPPI':", distinct_permutations("MISSISSIPPI"))
Output:
Distinct anagrams of 'BOOK': 12
Distinct anagrams of 'MISSISSIPPI': 34650
The following production script calculates lottery jackpot probabilities and evaluates combinatorial anagrams with automated self-checking assertions:
import math
from collections import Counter
def lottery_jackpot_odds(total_pool, pick_count, bonus_pool=1):
# Calculates 1 in X odds of winning jackpot
if pick_count > total_pool or total_pool <= 0:
raise ValueError("Invalid pool or pick count.")
main_combinations = math.comb(total_pool, pick_count)
total_tickets = main_combinations * bonus_pool
return total_tickets
def multinomial_arrangements(items_list):
# Number of unique sequences of items with duplicates
counts = Counter(items_list)
n = len(items_list)
denom = 1
for c in counts.values():
denom *= math.factorial(c)
return math.factorial(n) // denom
# --- Unit Assertions and Verification ---
# 1. 0! identity and small values
assert math.factorial(0) == 1
assert math.factorial(1) == 1
assert math.factorial(6) == 720
# 2. Standard 6/49 Lottery Odds: C(49, 6) = 13,983,816
odds_6_49 = lottery_jackpot_odds(total_pool=49, pick_count=6)
assert odds_6_49 == 13_983_816
# 3. Powerball Odds: C(69, 5) * 26 = 292,201,338
powerball_odds = lottery_jackpot_odds(total_pool=69, pick_count=5, bonus_pool=26)
assert powerball_odds == 292_201_338
# 4. Anagram permutation tests
assert multinomial_arrangements(list("BOOK")) == 12
assert multinomial_arrangements(list("PEPPER")) == 60 # 6! / (3! * 2!) = 720 / 12 = 60
print("Powerball jackpot odds: 1 in", f"{powerball_odds:,}")
print("Unique anagrams of 'PEPPER':", multinomial_arrangements(list("PEPPER")))
print("All combinatorial unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all factorial calculations interactively, evaluate massive 100! integers, benchmark recursive vs. iterative algorithms, and test lottery combinations in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.