ZeroDivisionError: Stop Divides by Zero in Python
ZeroDivisionError means a / // or % hit zero.
20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.
- ✓Basic arithmetic operators and variables in Python
- ✓Reading tracebacks to find the failing line number
- ✓Simple if-branches and function return values
- ZeroDivisionError fires when
/,//, or%meets a zero divisor — including0.0, empty-list means, and user input that parses to 0. - Fix it now: guard with
if divisor == 0: return defaultbefore the operation, since an explicit branch beats a rescue for expected empties. - You'll keep aggregates safe by checking
if not values: return 0.0beforesum(values) / len(values)or by usingstatistics.fmean, which still needs the guard. - Validate divisor input at the boundary with try/except around int() plus a zero check, so bad text and real zeros get different, clear messages.
Splitting a pizza among zero people has no answer — that's ZeroDivisionError. Your code asked Python to divide or take a remainder with a zero divisor, and Python refused instead of inventing a number. The divisor usually looks innocent: an empty count, a 0.0 sensor reading, or a blank form field. Check for zero before the math runs, and decide the answer when there's nothing to divide by.
Your pricing service throws ZeroDivisionError: division by zero on a line that's run a million times without complaint. Nothing in the math changed. What changed is the data: a new product category with zero completed orders, a sensor reporting 0.0 flow, a CSV whose count column arrived empty. Division assumes a nonzero world, and production keeps supplying zero.
This error is honest — Python won't return infinity for integers or silently emit NaN where you expected a price. But its honesty comes with a trap: developers wrap the division in try/except and move on, hiding every future zero instead of deciding what zero means. A conversion rate with zero visitors isn't an exception to swallow; it's a business state (no traffic) that deserves an explicit 0.0 and a logged reason.
You'll learn which three operators raise, why 0.0 counts as zero, how empty aggregates manufacture the error one call away from the crash, and the validation pattern that separates bad input from legitimate emptiness. By the end, every division in your codebase will have a decided answer for zero — not a rescue clause hoping nobody asks.
/ vs // vs %: All Three Operators Raise on Zero
Three operators share one rule: a zero divisor raises ZeroDivisionError every time. True division / computes the exact quotient, floor division // rounds toward negative infinity, and modulo % returns the remainder — but none of them will touch a zero divisor. Integers raise, floats raise, and Decimal raises its own DivisionByZero sibling. There is no quiet infinity for Python scalars, and that's deliberate: an invented number would poison every downstream total.
Floats surprise people most. Developers coming from spreadsheets or numpy expect 1.0/0.0 to yield inf, because IEEE 754 defines it and numpy follows suit with a warning. Pure Python chose differently — 1.0/0.0 raises ZeroDivisionError just like 1/0. If your pipeline mixes numpy arrays (inf with warning) and Python scalars (raise), the same zero produces two behaviors in two layers, and the scalar crash usually arrives first.
The takeaway is mechanical: every /, //, and % whose divisor derives from data needs a decision for zero. Grep for all three spellings, not just the slash — modulo with a zero cycle length and floor division over empty buckets crash exactly as hard as plain division. One audit pass over the three operators closes the whole class at once.
Guard the Divisor: Explicit Branches Beat try/except
The guard pattern is one branch before the math: if divisor == 0: return a decided default. For rates the default is usually 0.0 with a log line naming the entity. For averages over counts it may be None or NaN with a documented meaning. The key property is locality — the reader sees the zero decision next to the division, not in an except block three lines below that also catches unrelated errors.
Prefer if not divisor when both 0 and 0.0 (and empty collections upstream) should take the branch, but use == 0 when the divisor must be numeric and other falsy values would mask bugs. A divisor of None slipping through if not divisor hides a missing-data bug behind a rate of 0.0; an explicit == 0 lets None crash loudly where it should. Match the test to the type contract.
Reserve try/except ZeroDivisionError for genuinely exceptional zeros — divisors from third-party callbacks or plugin math you don't control. For your own data flow, the guard is clearer, faster, and honest about expectations. A function whose divisor can be zero isn't exceptional; it's a function with two valid inputs, and both deserve a visible branch.
Name the entity in the guard's log line (category, sensor, batch) so the zero case doubles as a data-quality signal instead of a silent default.
Empty Means and Rates: statistics.mean Needs Your Guard Too
Aggregates manufacture zero divisors one call away from the crash. statistics.mean([]) raises StatisticsError, sum(vals)/len(vals) raises ZeroDivisionError on len() == 0, and a hand-rolled rate over an empty query set dies the same way. The traceback points at the library or the len(), never at the empty filter that caused it — so developers fix the arithmetic while the real question (why was the set empty?) goes unasked.
The pattern is a two-line preamble wherever an aggregate meets possibly-empty data: if not values: return the empty-case answer. For dashboards that's usually 0.0; for scientific code it may be float('nan') with a documented 'no data' meaning; for strict pipelines it may be raising ValueError('empty batch') so emptiness pages instead of plotting. Pick per metric and write the choice where the next reader sees it.
statistics.fmean is faster for floats but equally strict about emptiness, so switching functions never removes the guard. What removes the crash is deciding the empty answer once per call site. Add the preamble to every mean, rate, and ratio over filtered data, and the brand-new category with zero rows becomes a boring 0.0 instead of a 90-minute outage.
Validate User and CSV Input: Separate Bad Text From Real Zeros
Divisors from outside your code arrive as strings, and strings carry two failure modes: text that isn't a number at all, and text that parses to zero. Collapsing both into one error message ('invalid divisor') confuses everyone — the user who typed 'abc' needs a different fix than the one who typed '0'. Validate in two stages with two messages, and the support tickets answer themselves.
Stage one parses: wrap int() or float() in try/except ValueError and report which field failed with the raw value quoted. Stage two checks zero explicitly and reports the field name plus what zero would mean ('batch size 0 would divide by zero; minimum is 1'). CSV flows get the same treatment per row, with bad rows quarantined to a review file instead of killing the 890,000-row load.
Never let raw input reach arithmetic. A parse-then-check boundary function returning either a positive number or a collected error keeps every downstream division provably nonzero. The 340 crashed pricing requests all traced to one unvalidated zero; a boundary check with a named-field message would have held them at the door with a 400 instead of a 500.
Reuse one boundary helper for forms, APIs, and CSV rows alike so every divisor enters arithmetic already parsed, positive, and logged with its field name.
Decide What Zero Means: 0.0, None, NaN, or Raise
Every guarded division still needs an answer, and the right answer depends on the metric's consumers. Reporting 0.0 suits rates and shares on dashboards — 'no visitors, no conversion' reads naturally and sorts correctly. Returning None suits optional analytics where 'unknown' must stay visually distinct from 'measured zero', provided callers handle it. Float NaN suits numeric pipelines that already propagate missingness, but only when every downstream step is NaN-aware. Raising ValueError suits strict pipelines where an empty batch means upstream breakage that must page.
The wrong default corrupts silently. Returning 0.0 for a sensor ratio that feeds a control loop tells the loop 'all clear' when the sensor is dead. Returning None into a template that formats with:.2f crashes the render. Propagating NaN into a revenue sum turns the quarter's total into NaN. Match the default to the consumer: dashboards get 0.0, nullable columns get None, numeric science gets NaN, and invariant-protected flows get an exception.
Document the choice in the helper's docstring with one line per metric: 'empty orders -> 0.0 (no traffic)'. That line is what stops the next developer from 'simplifying' your guard into a bare division during a refactor — the exact refactor that caused the 90-minute Spring outage.
Hunt Every Division: A 10-Minute Audit Before Launch
New categories, new sensors, and new CSV columns all supply first-time zeros, so audit divisions before launch instead of after the page. The audit takes ten minutes: grep for the three operators, list each divisor's source, and mark whether that source can be empty. Any divisor from a count, a filter result, user input, or an external event gets a guard or a boundary check before merge.
Automate the durable parts. A lint rule flagging bare sum(x)/len(x) patterns, a nightly job asserting every active category has nonzero visitors or an exemption, and a launch checklist item ('empty-category pricing render verified') each cost minutes and compound. The Spring launch checklist covered images and copy; one analytics line would have caught the zero before 340 shoppers did.
Re-verify after refactors that move arithmetic. Splitting a function often strands the guard in the old half while the division moves to the new half — the discount-helper outage in the companion article failed exactly this way. When arithmetic moves, the guard moves with it; review the diff for the divisor check the way you'd review it for the divisor itself.
File the audit output with the launch ticket so the next quarter's review starts from measured divisor sources instead of rediscovering them under pressure.
Zero-Visitor Category Crashed Pricing for 90 Minutes
- Guard every division whose divisor comes from data; a brand-new category supplies the zero your 8 months of history never did.
- Alert on per-endpoint error budgets, not global rates; 12% of one endpoint hid inside a green global dashboard for 22 minutes.
- Decide what zero means per metric — 0.0 rate with a logged reason beats a rescued exception nobody can interpret.
python -c "orders, visitors = 0, 0
print(repr(orders), repr(visitors))
print(orders / visitors)" — the last line raises ZeroDivisionError, proving the divisor. Then open the file and line from the traceback's second-to-last frame and log that divisor's value at runtime.python -c "for op in ('1/0', '1//0', '1%0'):
try:
eval(op)
except ZeroDivisionError as e:
print(op, '-> ZeroDivisionError:', e)". Then find every risky operator with grep -rn " / \| // \| % " pricing/ | head -20 and guard each divisor, not just the / ones.python -c "vals=[]; print(len(vals)); print(sum(vals)/len(vals))" raises ZeroDivisionError on the len. Fix with an explicit branch python -c "vals=[]; print(0.0 if not vals else sum(vals)/len(vals))" and find siblings with grep -rn "sum(.*)/len(" app/ | head -20.python -c "raw='0'; v=int(raw); print(repr(v), v == 0)" then grep -rn "int(input\|int(request" app/ | head -20 to find unvalidated entries. Validate at the boundary — try/except ValueError around int(), then an explicit == 0 branch with a message naming the field.python -c "print(1.0/0.0)" raises ZeroDivisionError (unlike numpy, which warns and yields inf). Confirm the value's type with python -c "v=0.0; print(repr(v), type(v).__name__, v == 0)" — 0.0 == 0 is True, so if not divisor catches both int and float zeros.| File | Command / Code | Purpose |
|---|---|---|
| zero_operators.py | for label, fn in (("/", lambda: 1 / 0), ("//", lambda: 1 // 0), ("%", lambda: 1 ... | / vs // vs % |
| zero_guard.py | def conversion_rate(orders, visitors, category="?"): | Guard the Divisor |
| zero_empty_mean.py | def safe_mean(values, default=0.0): | Empty Means and Rates |
| zero_validate_input.py | def parse_divisor(raw, field="batch_size"): | Validate User and CSV Input |
| zero_what_zero_means.py | def rate_strict(orders, visitors): | Decide What Zero Means |
| zero_audit.py | sample = open(__file__).read() | Hunt Every Division |
Key takeaways
Common mistakes to avoid
5 patternsWrapping division in bare try/except and moving on
Guarding / but forgetting // and %
Assuming 1.0/0.0 returns inf like numpy
Using if not divisor on possibly-None values
Skipping the launch-checklist analytics line
Interview Questions on This Topic
Which Python operators raise ZeroDivisionError, and does 1.0/0.0 raise?
Frequently Asked Questions
20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.
That's Errors. Mark it forged?
5 min read · try the examples if you haven't