Skip to content

Fix: Python ZeroDivisionError: division by zero

FixDevs ·

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 zero

Other common variations:

ZeroDivisionError: integer division or modulo by zero
ZeroDivisionError: float division by zero
ZeroDivisionError: integer modulo by zero

The 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 is 0.
  • A counter or accumulator was never incremented, so it stays at its initial value of 0.
  • A database field or API response returns 0 or None (which gets cast to 0).
  • 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 sense

This 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 / b

Pro Tip: When you choose a default return value, think about what downstream code expects. Returning 0 when the division is undefined can silently produce wrong results. Returning None forces 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 value

This 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_rate

If 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 results

Note: 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)  # ZeroDivisionError

len(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.0

Or 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.0

For 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 zero

This commonly occurs in:

  • Pagination logic: total_items % items_per_page when items_per_page is zero.
  • Cyclic index calculations: index % cycle_length when 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 % b

For 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_pages

Note 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.DivisionByZero

decimal.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)  # NaN

For 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 undefined

Note: 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 divide

You 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.0

Replace 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 replace inf values. inf is not NaN in pandas. You need .replace([np.inf, -np.inf], 0) first, or use pd.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 zero

This 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 // 60 to get minutes.
  • Chunk processing: total_items // chunk_size to 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 // interval

The divmod() built-in is also affected:

quotient, remainder = divmod(100, 0)  # ZeroDivisionError

Wrap 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 / time

Use 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 inf would 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 default

This 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 / b

The 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 / count

This 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles