Fix: Python ZeroDivisionError: division by zero
Quick Answer
Resolve Python's ZeroDivisionError by checking divisors, using try/except, handling empty lists, fixing modulo and Decimal edge cases, and managing numpy/pandas division.
The Error
You run a Python script that performs a division, and Python throws this:
Traceback (most recent call last):
File "app.py", line 3, in <module>
result = 100 / 0
ZeroDivisionError: division by zeroOther common variations:
ZeroDivisionError: integer division or modulo by zeroZeroDivisionError: float division by zeroZeroDivisionError: integer modulo by zeroThe operation that triggered it might be obvious (a literal / 0), but more often the zero sneaks in through a variable, a function return value, or an empty dataset. Regardless of the source, Python refuses to divide by zero and raises ZeroDivisionError every time.
Why This Happens
Division by zero is mathematically undefined. Python enforces this strictly across all division-related operations: /, //, %, and divmod(). When the right-hand operand evaluates to zero, Python raises ZeroDivisionError immediately.
The tricky part is that the zero rarely appears as a literal in your code. It shows up because:
- A variable holds zero due to user input, a missing configuration value, or a default that was never updated.
- A list or query result is empty, and you divide by
len(result)which is0. - A counter or accumulator was never incremented, so it stays at its initial value of
0. - A database field or API response returns
0orNone(which gets cast to0). - A calculation chain produces zero as an intermediate value, and a subsequent step divides by it.
Understanding where the zero comes from is the real fix. The sections below cover every common scenario and how to handle each one properly.
Fix 1: Check for Zero Before Dividing
The most straightforward fix is a guard clause. Check whether the divisor is zero before performing the division:
def safe_divide(a, b):
if b != 0:
return a / b
return 0 # or None, or any default that makes senseThis works for simple cases. Use it when you have a single division and a clear default value.
For cases where zero means “no data available,” returning None is often better than returning 0, because it signals to the caller that the result is not a real number:
def calculate_rate(total, count):
if count == 0:
return None
return total / count
rate = calculate_rate(500, 0)
if rate is not None:
print(f"Rate: {rate}")
else:
print("Not enough data to calculate rate")When comparing floats, be careful with exact equality checks. Floating-point arithmetic can produce values extremely close to zero but not exactly zero. If your divisor comes from float calculations, consider using a threshold:
import math
def safe_divide_float(a, b, tolerance=1e-10):
if math.isclose(b, 0, abs_tol=tolerance):
return 0.0
return a / bPro Tip: When you choose a default return value, think about what downstream code expects. Returning
0when the division is undefined can silently produce wrong results. ReturningNoneforces the caller to handle the edge case explicitly, which usually leads to more correct programs.
Fix 2: Use try/except ZeroDivisionError
When you cannot easily predict whether the divisor will be zero, or when the division is buried inside a larger expression, wrap it in a try/except block:
try:
result = numerator / denominator
except ZeroDivisionError:
result = 0 # fallback valueThis is the Pythonic approach when zero is an exceptional case rather than a regular one. It follows the “easier to ask forgiveness than permission” (EAFP) principle that Python encourages.
You can also catch it in more complex expressions:
def compute_metrics(hits, misses, errors):
total = hits + misses + errors
try:
hit_rate = hits / total
error_rate = errors / total
except ZeroDivisionError:
hit_rate = 0.0
error_rate = 0.0
return hit_rate, error_rateIf you need to know which specific division failed, nest them separately or log the context:
import logging
def calculate_ratios(a, b, c):
results = {}
for name, num, den in [("ratio_ab", a, b), ("ratio_ac", a, c)]:
try:
results[name] = num / den
except ZeroDivisionError:
logging.warning(f"{name}: denominator is zero (num={num}, den={den})")
results[name] = None
return resultsNote: Do not use a bare except clause to catch ZeroDivisionError. Always catch the specific exception. A bare except will swallow unrelated errors and make debugging much harder.
Fix 3: Fix Division in Averages (Empty Collections)
One of the most common sources of ZeroDivisionError is calculating an average on an empty list:
scores = []
average = sum(scores) / len(scores) # ZeroDivisionErrorlen(scores) is 0, and sum(scores) is 0, so you get 0 / 0.
Fix it by checking the length first:
scores = []
if scores:
average = sum(scores) / len(scores)
else:
average = 0.0Or use Python’s statistics module, which raises a more descriptive StatisticsError on empty data and handles edge cases correctly:
from statistics import mean, StatisticsError
try:
average = mean(scores)
except StatisticsError:
average = 0.0For more control, write a reusable helper:
def safe_average(values, default=0.0):
if not values:
return default
return sum(values) / len(values)This pattern also applies to grouped aggregations. If you group data and some groups end up empty, each empty group triggers the error:
from collections import defaultdict
grouped = defaultdict(list)
for item in data:
grouped[item["category"]].append(item["value"])
averages = {}
for category, values in grouped.items():
averages[category] = safe_average(values)This is closely related to handling None values in collections. If you are also running into issues with None showing up where you expect real values, see how to fix NoneType not subscriptable errors for common patterns.
Fix 4: Fix Modulo Operations (% 0)
The modulo operator % also raises ZeroDivisionError when the right operand is zero:
value = 10
divisor = 0
remainder = value % divisor # ZeroDivisionError: integer modulo by zeroThis commonly occurs in:
- Pagination logic:
total_items % items_per_pagewhenitems_per_pageis zero. - Cyclic index calculations:
index % cycle_lengthwhen the cycle length is not set. - Input validation: checking if a number is even/odd with
n % 2, but the variable is actually something other than expected.
Fix it the same way as regular division:
def safe_modulo(a, b, default=0):
if b == 0:
return default
return a % bFor pagination specifically:
def paginate(total_items, items_per_page):
if items_per_page <= 0:
raise ValueError("items_per_page must be positive")
full_pages = total_items // items_per_page
remainder = total_items % items_per_page
total_pages = full_pages + (1 if remainder > 0 else 0)
return total_pagesNote the <= 0 check instead of == 0. Negative page sizes do not make sense either, and catching them early prevents confusing behavior. Similar defensive checks apply whenever you read values from external input. If your code reads values from dictionaries and sometimes gets a KeyError instead of a valid number, check out fixing Python KeyError.
Fix 5: Fix Decimal and Float Division Edge Cases
Python’s decimal.Decimal type handles division by zero differently depending on the context settings:
from decimal import Decimal, DivisionByZero
result = Decimal("10") / Decimal("0") # Raises decimal.DivisionByZerodecimal.DivisionByZero is a subclass of both decimal.DecimalException and ZeroDivisionError, so a try/except ZeroDivisionError catches it. But you can also configure the decimal context to handle it differently:
from decimal import Decimal, localcontext, InvalidOperation
with localcontext() as ctx:
ctx.traps[InvalidOperation] = False
result = Decimal("0") / Decimal("0") # Returns Decimal('NaN') instead of raising
print(result) # NaNFor float division, Python raises ZeroDivisionError for 1.0 / 0.0, but float('inf') and float('nan') exist as values. You can use them as sentinels:
def divide_with_inf(a, b):
try:
return a / b
except ZeroDivisionError:
if a > 0:
return float('inf')
elif a < 0:
return float('-inf')
else:
return float('nan') # 0/0 is undefinedNote: Be careful propagating inf and nan through calculations. They are contagious — any arithmetic with nan produces nan, and comparisons with nan always return False. Check for them with math.isinf() and math.isnan().
If your code converts strings to numbers and sometimes gets unexpected values, that can also lead to a zero divisor. See fixing invalid literal for int() for how to handle those conversion errors.
Fix 6: Fix numpy and pandas Division by Zero
In numpy, dividing by zero in an array does not raise ZeroDivisionError. Instead, it produces inf or nan and emits a runtime warning:
RuntimeWarning: divide by zero encountered in divideYou can suppress the warning and handle the result:
import numpy as np
a = np.array([10, 20, 30])
b = np.array([2, 0, 5])
# Use np.divide with where to avoid the warning entirely
result = np.divide(a, b, out=np.zeros_like(a, dtype=float), where=b != 0)
print(result) # [5. 0. 6.]The where parameter tells numpy to only perform the division where b != 0. Positions where b == 0 get the value from the out array (zeros in this case).
For simpler cases, use np.errstate to suppress the warning and then replace the invalid values:
with np.errstate(divide='ignore', invalid='ignore'):
result = np.where(b != 0, a / b, 0)In pandas, division by zero in a Series or DataFrame also produces inf or NaN:
import pandas as pd
df = pd.DataFrame({"revenue": [100, 200, 300], "users": [10, 0, 15]})
df["revenue_per_user"] = df["revenue"] / df["users"]
print(df["revenue_per_user"])
# 0 10.0
# 1 inf
# 2 20.0Replace the infinities with a sensible default:
import numpy as np
df["revenue_per_user"] = df["revenue"] / df["users"]
df["revenue_per_user"] = df["revenue_per_user"].replace([np.inf, -np.inf], 0)
df["revenue_per_user"] = df["revenue_per_user"].fillna(0)Or prevent the issue entirely using where:
df["revenue_per_user"] = df["revenue"].where(df["users"] != 0, 0) / df["users"].where(df["users"] != 0, 1)Common Mistake: Using
.fillna(0)alone does not replaceinfvalues.infis notNaNin pandas. You need.replace([np.inf, -np.inf], 0)first, or usepd.options.mode.use_inf_as_na = True(deprecated in newer pandas — prefer explicit replacement).
Fix 7: Fix Integer Division (//) by Zero
Floor division // raises the same ZeroDivisionError:
result = 10 // 0 # ZeroDivisionError: integer division or modulo by zeroThis is the exact same error message you get with % because CPython internally uses divmod() for both operations.
Floor division commonly appears in:
- Index calculations: converting a flat index to row/column with
index // columns. - Time conversions:
total_seconds // 60to get minutes. - Chunk processing:
total_items // chunk_sizeto get the number of full chunks.
Guard it the same way:
def safe_floor_divide(a, b, default=0):
if b == 0:
return default
return a // b
# Time conversion example
def seconds_to_minutes(total_seconds, interval=0):
if interval == 0:
return total_seconds # no interval means return raw seconds
return total_seconds // intervalThe divmod() built-in is also affected:
quotient, remainder = divmod(100, 0) # ZeroDivisionErrorWrap it:
def safe_divmod(a, b, default=(0, 0)):
if b == 0:
return default
return divmod(a, b)If you hit this error while processing list indices, you may also want to review how to fix IndexError: list index out of range to handle other index-related failures.
Fix 8: Use math.inf or Default Values as Fallback
Sometimes zero in the denominator has a mathematical interpretation. In rate calculations, physics formulas, or financial models, dividing by zero can mean “infinite” or “undefined.” In those cases, returning a sentinel value is better than returning 0:
import math
def calculate_speed(distance, time):
if time == 0:
return math.inf # Instantaneous = infinite speed
return distance / timeUse math.inf when:
- The result represents a rate or ratio where zero denominator means “unbounded.”
- Downstream code can handle
inf(e.g., sorting, comparison). - You want to preserve the mathematical meaning.
Use a default value when:
- The result will be displayed to users (showing “Infinity” is confusing).
- The result feeds into further calculations where
infwould break things. - Business logic defines a specific fallback.
def calculate_conversion_rate(conversions, visitors, default=0.0):
if visitors == 0:
return default
return conversions / visitors
# Display-friendly version
def format_rate(conversions, visitors):
if visitors == 0:
return "N/A"
rate = (conversions / visitors) * 100
return f"{rate:.1f}%"You can also build a generic safe division utility that covers multiple fallback strategies:
import math
def divide(a, b, on_zero="default", default=0):
"""
Safe division with configurable zero handling.
on_zero options:
"default" - return the default value
"inf" - return math.inf (preserves sign)
"nan" - return float('nan')
"raise" - re-raise ZeroDivisionError
"""
if b != 0:
return a / b
if on_zero == "inf":
return math.copysign(math.inf, a) if a != 0 else float('nan')
elif on_zero == "nan":
return float('nan')
elif on_zero == "raise":
raise ZeroDivisionError(f"division by zero: {a} / {b}")
else:
return defaultThis centralizes your division-by-zero policy and keeps the rest of your code clean.
If you find that None keeps appearing as a divisor because an object attribute was never set, that is a different problem. Check fixing AttributeError: NoneType has no attribute for how to trace and fix those None sources.
Still Not Working?
If you have added guards and the error still shows up, or if you need to handle division-by-zero more robustly in a production system, try these approaches.
Log the Division Context
When ZeroDivisionError appears in production and you cannot reproduce it locally, add logging around the division to capture the operands:
import logging
logger = logging.getLogger(__name__)
def compute_ratio(a, b):
logger.debug(f"compute_ratio called with a={a!r}, b={b!r}")
if b == 0:
logger.error(f"Division by zero in compute_ratio: a={a!r}, b={b!r}",
stack_info=True)
return None
return a / bThe stack_info=True parameter includes the call stack in the log, which helps trace where the zero value originated. This is especially useful in large codebases where a value passes through multiple functions before reaching the division.
Create Custom Exception Handling
For applications where different types of division-by-zero need different handling, define custom exceptions:
class InvalidDivisorError(Exception):
def __init__(self, operation, numerator, denominator, context=None):
self.operation = operation
self.numerator = numerator
self.denominator = denominator
self.context = context or {}
super().__init__(
f"Invalid divisor in {operation}: {numerator} / {denominator} "
f"(context: {context})"
)
def calculate_metric(value, count, metric_name="unknown"):
if count == 0:
raise InvalidDivisorError(
operation=metric_name,
numerator=value,
denominator=count,
context={"source": "calculate_metric"}
)
return value / countThis gives calling code enough information to decide how to handle each case, log it properly, or surface it to the user.
Handle Division in Database Queries
If the division happens inside a database query, fix it at the query level rather than in Python. Most databases have their own way to handle division by zero:
PostgreSQL:
SELECT NULLIF(total, 0) AS safe_total,
revenue / NULLIF(total, 0) AS rate
FROM metrics;NULLIF(total, 0) returns NULL when total is 0, and PostgreSQL returns NULL for revenue / NULL instead of raising an error.
MySQL:
SELECT IF(total = 0, 0, revenue / total) AS rate
FROM metrics;SQLite:
SELECT CASE WHEN total = 0 THEN 0 ELSE revenue / total END AS rate
FROM metrics;Handling it in SQL is more efficient than fetching the data into Python and dividing there, especially for large result sets. It also prevents the error from ever reaching your Python code.
Trace the Source of the Zero
If the zero is coming from upstream and you cannot figure out where, use Python’s -W error flag to turn all warnings into errors, or add an assertion at the point of entry:
def process_data(data):
assert data.get("divisor") != 0, (
f"Unexpected zero divisor in data: {data}"
)
return data["value"] / data["divisor"]Assertions are stripped in optimized mode (python -O), so use them for development and testing only. In production, use explicit checks with logging as shown above.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS Lambda Unable to import module / Runtime.ImportModuleError
How to fix the AWS Lambda Runtime.ImportModuleError and Unable to import module error caused by wrong handler paths, missing dependencies, layer issues, and packaging problems.
Fix: Python TypeError: unhashable type: 'list'
Learn why Python raises TypeError unhashable type list, dict, or set and how to fix it when using dictionary keys, sets, groupby, dataclasses, and custom classes.
Fix: Django Forbidden (403) CSRF verification failed
How to fix Django 403 CSRF verification failed error caused by missing CSRF tokens, AJAX requests, cross-origin issues, HTTPS misconfig, and session problems.
Fix: FastAPI 422 Unprocessable Entity (validation error)
How to fix FastAPI 422 Unprocessable Entity error caused by wrong request body format, missing fields, type mismatches, query parameter errors, and Pydantic validation.