Python errors feel scary until you realize they’re mostly repeating patterns. Once you can read a traceback and recognize the usual suspects, debugging stops being a guessing game. This post is a practical “field guide” to the top 25 Python errors—with the 1-line fixes you can apply fast, plus a workflow that helps you prevent the same mistakes from coming back.
Quickstart
If you’re actively stuck on an error right now, do this in order. These steps are designed to get you from “it crashed” to “fixed + won’t regress” as quickly as possible.
1) Read the traceback from the bottom
The last line is the punchline: TypeError, NameError, KeyError, etc.
Then look at the last frame in your code (not the standard library).
- Start at the last line: exception type + message
- Jump to the last “your file” entry in the stack
- Confirm the variable values at that exact line
2) Make the crash reproducible
If you can’t reproduce it, you can’t reliably fix it. Try to run the smallest possible command that triggers the same error.
- Save the exact input that caused the failure
- Reduce to a minimal snippet (delete until it still breaks)
- Re-run after each change (small steps)
3) Inspect values like a detective
Most Python errors are not “mysterious”—they’re “this value is not what you think it is.”
type(x)(is it a list, dict, None?)repr(x)(hidden whitespace, quotes, escapes)len(x),x.keys(),dir(x)(shape of the data)
4) Add a fast “guardrail”
Fix the bug, then add something that prevents it: an assertion, a type, a test, or a validation step.
- Validate external inputs (files, JSON, user text)
- Prefer explicit checks:
if x is None: - Add a tiny test that reproduces the bug
Developer flags surface common issues earlier (warnings, resource leaks, faulthandler output). This won’t replace real debugging, but it shrinks your search space.
# create a clean environment (recommended for import/install issues)
python -m venv .venv
# macOS/Linux:
source .venv/bin/activate
# Windows (PowerShell):
# .\.venv\Scripts\Activate.ps1
python -m pip install -U pip
# optional: fast static checks (skip if you want pure stdlib)
python -m pip install -U ruff
# run with extra diagnostics
python -X dev -X faulthandler your_script.py
ruff check .
A 1-line fix is the smallest change that addresses the root cause (not a band-aid). Sometimes that line is “use the right method,” sometimes it’s “add a guard,” and sometimes it’s “import the correct symbol.” The sections below show the fix and the reasoning so you can generalize.
Overview
“Top 25 Python errors” isn’t about memorizing messages—it’s about learning the small set of mental models Python is enforcing: names must exist, types must match operations, indexes must be in range, keys must be present, and indentation must be consistent.
How to use this post (fast)
- Stuck now? Use Step-by-step to debug systematically.
- Recognize the error? Jump to the Top 25 list and apply the 1-liner.
- Want fewer bugs long-term? Read Common mistakes + Cheatsheet.
What’s inside
- A repeatable debugging workflow (works for almost any exception)
- The 25 most common Python errors with 1-line fixes
- Pitfalls that create “weird” bugs (mutable defaults, shadowing, imports)
- FAQ + a scan-fast checklist you can bookmark
What this is not
- A list of random “gotchas” without context
- A cheat sheet that ignores why the error happened
- A “just wrap it in try/except” approach
If your “fix” is deleting an error by hiding it (blanket except:, returning defaults everywhere),
you’ll ship bugs that are harder to diagnose later. Prefer fixes that make the data and control flow explicit.
Core concepts
The fastest debuggers don’t memorize messages—they understand what Python is trying to protect: predictable name lookup, consistent object behavior, and safe access to containers. These core concepts turn “random error strings” into a small set of categories.
1) Tracebacks are maps, not blame
A traceback is a stack of frames: “here’s where we were, and here’s how we got there.” The most useful frame is usually the last one inside your project. The bottom line tells you the exception type and message—treat it as a search query for your brain.
A quick way to interpret a traceback
| Part | What it means | What you do |
|---|---|---|
| Exception type | TypeError, NameError, etc. |
Classify the bug category |
| Message | Often names the exact mismatch | Verify variable types/values |
| Stack frames | Call chain leading to the crash | Jump to the last “your code” frame |
| Line of code | The operation that failed | Ask “what must be true for this to work?” |
2) “Names” and scope rules are strict (and that’s good)
If Python says a name doesn’t exist (NameError) or is used before assignment (UnboundLocalError),
it’s telling you exactly where your mental model diverges from reality. Scope is determined at compile time:
if you assign to a name anywhere in a function, Python treats it as local unless declared global/nonlocal.
3) Types define what operations are legal
TypeError is Python’s way of saying: “this object doesn’t support that operation.”
The fix is usually one of three things: convert the type, call the method you actually meant, or change the data shape.
The fastest move is to print the type at the failing line.
4) Containers don’t guess: indexes and keys must exist
Lists/tuples care about indexes (IndexError), dicts care about keys (KeyError).
In production code, you rarely want a crash on missing data—so you use guards:
if key in d, d.get(key), if i < len(xs), or default values.
5) Imports depend on your environment, not your hopes
Many “ImportError” problems are really “I’m running a different Python than I think.”
A virtual environment keeps dependencies consistent, and running with python -m helps ensure you’re using the same interpreter.
At the failing line, ask: “What must be true for this to work?” Then verify those conditions with prints, assertions, or a debugger.
Step-by-step
This is a practical workflow you can apply to almost any Python exception. It’s intentionally boring—because boring workflows are repeatable, and repeatable workflows ship code.
Step 1 — Confirm the failing line and the failing value
Go to the last frame in your code and look at the exact operation.
Then confirm the value(s) on that line (type, repr, shape).
If you want a single best tool built into Python, it’s the debugger hook: breakpoint().
def debug_here(**vars_to_watch):
# tiny helper: call with debug_here(x=x, y=y) to see types and reprs
for k, v in vars_to_watch.items():
print(f"{k}: type={type(v).__name__}, repr={v!r}")
def parse_port(value):
# common crash: ValueError when value isn't numeric
debug_here(value=value)
if value is None:
return 0 # 1-line fix: guard None explicitly
return int(str(value).strip()) # 1-line fix: normalize before int()
# Use breakpoint() when the bug isn't obvious from prints
def handler(payload):
port = payload.get("port")
breakpoint() # drop into debugger: inspect payload, port, etc.
return parse_port(port)
In the debugger, you usually want three checks first: type(x), x, and x!r (repr).
Many “impossible” bugs are whitespace, None, or the wrong container shape.
Step 2 — Apply the smallest correct fix
A good fix changes the program so the bad state can’t occur (or is handled explicitly). Below are the Top 25 Python errors you’ll see in real code, each with a 1-line fix and a short “why.”
Top 25 Python errors and the 1-line fixes
| # | Error (common message) | What it usually means | 1-line fix (typical) |
|---|---|---|---|
| 1 | SyntaxError | Python can’t parse the code | Fix the token: missing :, quote, bracket, or bad indentation |
| 2 | IndentationError | Mixed tabs/spaces or wrong block alignment | Convert tabs → spaces and align blocks (most editors: “convert indentation”) |
| 3 | NameError: name x is not defined |
Variable/function wasn’t created in this scope | Define it or pass it in: def f(x): ... / x = ... |
| 4 | UnboundLocalError | You assign to a name in a function, making it local, but read it first | Initialize before use: x = None (or add nonlocal/global if intended) |
| 5 | TypeError: ‘NoneType’ object is not ... | You’re operating on None |
Guard: if x is None: return ... |
| 6 | TypeError: can only concatenate str (not "int") to str | Mixing types in string operations | Convert: msg = "n=" + str(n) (or use f-strings) |
| 7 | TypeError: 'str' object is not callable | You overwrote a function name with a string (shadowing) | Rename the variable (e.g., don’t use str, list, open) |
| 8 | TypeError: 'int' object is not subscriptable | You used [...] on a non-container |
Use the right value: ensure x is a list/dict, or drop the index |
| 9 | AttributeError: 'X' object has no attribute 'y' | Wrong object type, wrong attribute, or None |
Check type/None first: assert obj is not None (or call correct method) |
| 10 | IndexError: list index out of range | Index is outside list bounds | Guard: if i < len(xs): ... (or iterate directly) |
| 11 | KeyError: 'missing' | Dict key doesn’t exist | Use d.get("missing") or check if key in d |
| 12 | ValueError: invalid literal for int() | Conversion failed (bad format) | Normalize input: int(str(x).strip()) (or validate with isdigit()) |
| 13 | ValueError: too many values to unpack | Unpacking expects fewer items than provided | Match arity: a, b, *rest = items |
| 14 | ValueError: not enough values to unpack | Unpacking expects more items than provided | Provide defaults: a, b = (items + [None, None])[:2] (or check length) |
| 15 | ZeroDivisionError | Division by zero | Guard: if denom == 0: return 0 (or raise a clearer error) |
| 16 | ImportError: cannot import name ... | Wrong symbol name, circular import, or version mismatch | Import the module then access: import pkg; pkg.name (or fix circular import) |
| 17 | ModuleNotFoundError | Package not installed in this interpreter/venv | Install via the same Python: python -m pip install package |
| 18 | FileNotFoundError | Path is wrong or relative path differs | Use an absolute path (or build relative to __file__) |
| 19 | PermissionError | OS denies access to file/dir | Write to a permitted location (or fix permissions) |
| 20 | IsADirectoryError | You tried to open a directory as a file | Point to a file path (or list directory contents) |
| 21 | UnicodeDecodeError | Wrong encoding assumption when reading bytes | Specify encoding: open(path, encoding="utf-8") |
| 22 | JSONDecodeError | Invalid JSON (trailing commas, single quotes, partial file) | Validate/print raw content; ensure strict JSON formatting |
| 23 | AssertionError | An assertion condition failed | Fix the violated assumption or replace assert with explicit error handling |
| 24 | RecursionError: maximum recursion depth exceeded | Unbounded recursion or cycles | Add a base case (or switch to an iterative loop) |
| 25 | RuntimeError: event loop is closed / already running | Async loop misuse (often in notebooks) | Use await in async contexts; avoid nested asyncio.run() |
Step 3 — Prevent the bug from returning
After you apply the 1-liner, add a small guardrail. The goal isn’t “never fail”—it’s “fail clearly, early, and in the right place.”
Guardrails that pay off fast
- Add input checks at boundaries (files, JSON, HTTP)
- Prefer
d.get(key)when missing keys are normal - Use
breakpoint()for non-obvious state bugs - Write one regression test per “real crash”
Signals you fixed the root cause
- You can explain the bug in one sentence
- You can reproduce it reliably (before) and not (after)
- The fix reduces ambiguity (clear types, clear guards)
- You didn’t hide the error with a blanket exception
Common mistakes
The errors above are the symptoms. These are the patterns that cause them repeatedly. Fixing these habits once can remove a surprising amount of day-to-day debugging.
Mistake 1 — Shadowing built-ins (list, str, dict)
You assign str = "hello" and later try to call str(...).
This creates “object is not callable” and similar confusion.
- Fix: rename the variable (e.g.,
text,items,mapping). - Prevention: linters catch this early.
Mistake 2 — Mutable default arguments
Default args are evaluated once, not per call. That means data “leaks” between calls.
- Fix: default to
Noneand create inside the function. - Symptom: “why does my list keep growing?”
# BAD: the same list is reused across calls
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
# GOOD: new list each time (1-line fix: default None + create inside)
def add_tag(tag, tags=None):
tags = [] if tags is None else tags
tags.append(tag)
return tags
Mistake 3 — Using == vs is incorrectly
is checks identity (same object), not equality (same value).
Use is for singletons like None.
- Fix:
if x is None:(notx == None). - Fix: for value comparison use
==.
Mistake 4 — Assuming external data always has the shape you expect
Most KeyError and TypeError issues come from unvalidated JSON, CSV, or user inputs.
- Fix: validate at the boundary (presence, type, range).
- Fix: use
.get()with defaults when missing is normal.
Mistake 5 — Iterating by index when you don’t need to
Manual indexing is a common path to IndexError.
Python gives you safer patterns.
- Fix: iterate directly:
for x in xs: - Fix: need the index?
for i, x in enumerate(xs):
Mistake 6 — Catching exceptions too broadly
except Exception: can be appropriate at system boundaries, but inside core logic it hides root causes.
- Fix: catch specific exceptions:
except (ValueError, KeyError) as e: - Fix: log and re-raise when you can’t recover.
Mistake 7 — Relative-path surprises
Your working directory depends on how you run the program (IDE vs CLI vs cron).
That’s why relative paths fail with FileNotFoundError.
- Fix: build paths relative to the script (or configure a project root).
- Fix: print the current dir once:
os.getcwd()when debugging.
Mistake 8 — Running the wrong Python interpreter
“It works on my machine” often means “I installed it in a different interpreter.”
- Fix: install with
python -m pip ...(matches the running interpreter). - Fix: use a venv and activate it consistently.
When you see a weird error, don’t “try random changes.”
First, print type(x) and repr(x) at the failing line.
You’ll fix more bugs in 30 seconds than in 30 minutes of guessing.
FAQ
How do I read a Python traceback correctly?
Start at the bottom: the final line tells you the exception type and message. Then go up to the last stack frame that points to your file. That line is where the failure happened; the earlier frames explain how you got there.
Why do I get ModuleNotFoundError even after installing the package?
You likely installed into a different Python interpreter than the one running your program.
Use python -m pip install ... and a virtual environment to ensure install and runtime match.
What’s the difference between KeyError and AttributeError?
KeyError is for dictionaries when a key is missing (d["k"]).
AttributeError is for objects when a property/method doesn’t exist (obj.k).
If missing data is normal, use d.get("k") or validate earlier.
When should I use try/except?
Use it when you can recover (e.g., parsing user input, reading optional files) or at system boundaries (CLI entrypoint, API handler) to return a clean error message. Inside core logic, prefer guards and explicit checks; catch only specific exceptions you expect.
Why does Python say “object is not callable”?
It usually means you overwrote a function name with a variable (shadowing), or you forgot parentheses.
Example: print = "oops" makes print("hi") fail. Rename the variable to restore the callable.
How do I stop errors from coming back after I fix them?
Add a guardrail: validate inputs, assert key invariants, write a tiny regression test, and avoid hiding exceptions. The goal is to turn “mystery crash” into “clear failure at the boundary.”
Cheatsheet
Bookmark this. It’s the quickest way to turn a scary traceback into an actionable fix.
Debugging flow (90 seconds)
- Read the last line: exception type + message
- Open the last frame in your code
- Print:
type(x)+repr(x)for the suspicious values - Ask: “What must be true for this line to work?”
- Apply the smallest fix (conversion / guard / correct method)
- Add a guardrail (validation, assertion, test)
Fast mapping: error → first thing to check
| Error type | First check | Common 1-liner |
|---|---|---|
| NameError / UnboundLocalError | Scope & where the name is assigned | Initialize: x = None or pass parameter |
| TypeError | type(x) at failing line |
Convert: str(x), int(...), or use correct method |
| KeyError | Is the key present? Is data shape stable? | d.get(key) or if key in d |
| IndexError | Length vs index; why index exists at all | if i < len(xs): or iterate directly |
| Import errors | Which interpreter am I using? | python -m pip install ... and venv |
Add breakpoint() at the failing line, inspect the exact values, and make one small change.
Most bugs are “wrong type” or “unexpected None” in disguise.
Wrap-up
Python errors are mostly a friendly constraint system: names must exist, types must match operations, and containers don’t guess. Once you internalize those rules, the “Top 25 Python errors” stop being a list of scary messages and become a short set of patterns you can fix quickly and confidently.
Next time you hit an exception: read the last line, jump to your code frame, inspect the real values, apply the smallest fix, and add one guardrail so it doesn’t happen again. If you want to go deeper, check out the related posts below for faster Python, cleaner code structure, and async patterns that don’t bite.
Next actions
- Save this post and keep the Cheatsheet open while you code
- Pick 2 common errors you hit and add guardrails (validation + test)
- Adopt one habit this week: avoid shadowing built-ins, or stop using mutable defaults
Quiz
Quick self-check (demo). This quiz is auto-generated for programming / python / errors.