Skip to main content
Python•Last Updated : 20 Oct 2026
~40 min read

Errors and Exceptions in Python

Reading tracebacks, the common exception types, handling errors with try, except, else and finally, raising your own exceptions, and writing programs that survive bad input.

When things go wrong at runtime

A program can be perfectly valid Python and still fail while it runs: the user types letters where a number belongs, a file is missing, a divisor turns out to be zero. Python signals such a problem by raising an exception, an object that describes what went wrong.

If nothing handles the exception, the program stops and prints a traceback. Read it from the bottom: the last line gives the exception's type and message, and the lines above show the chain of calls that led there, ending with the line that failed.

Exceptions are not only for crashes. They are Python's normal way of reporting "this operation could not be done", and handling them is part of writing reliable programs.

Exceptions you will meet most

ExceptionRaised whenExample
ValueErrorThe type is right but the value is unusableint("12abc")
TypeErrorAn operation gets the wrong type"3" + 4
KeyErrorA dictionary key is missing{}["name"]
IndexErrorA sequence index is out of range[1, 2][5]
ZeroDivisionErrorDividing by zero10 / 0
FileNotFoundErrorOpening a file that does not existopen("missing.txt")
AttributeErrorA value has no such attribute or method(5).upper()

Handling an exception with try and except

Put the code that might fail in a try block and the recovery in an except block. If the try block raises an exception of the named type, Python jumps straight to the matching except block, runs it, and then carries on after the whole statement. If nothing goes wrong, the except block is skipped.

Lines in the try block after the failing line do not run. Keep the try block small, containing only the operation that can fail, so it is clear what you are protecting.

PythonConverting text safely
1for text in ["42", "forty-two", "7"]:
2 try:
3 number = int(text)
4 print("Converted:", number)
5 except ValueError:
6 print("Not a whole number:", repr(text))
7print("The program keeps going.")
Output
Converted: 42
Not a whole number: 'forty-two'
Converted: 7
The program keeps going.

Catching the right exceptions

Name the exception types you expect. A bare except: (or except Exception:) also catches mistakes you did not anticipate, including typos in your own code, and turns a clear error into a silent wrong result.

You can stack several except clauses for different problems, or group types in a tuple. except ValueError as error: binds the exception object, so you can show or log its message.

Exception types form a hierarchy: catching LookupError covers both KeyError and IndexError, and nearly everything inherits from Exception.

PythonDifferent problems, different handling
1def safe_ratio(values, i, j):
2 try:
3 return values[i] / values[j]
4 except IndexError:
5 return "index out of range"
6 except ZeroDivisionError:
7 return "cannot divide by zero"
8 except TypeError as error:
9 return f"bad data: {error}"
10
11data = [10, 0, "5"]
12print(safe_ratio(data, 0, 3))
13print(safe_ratio(data, 0, 1))
14print(safe_ratio(data, 2, 0))
15print(issubclass(KeyError, LookupError), issubclass(IndexError, LookupError))
Output
index out of range
cannot divide by zero
bad data: unsupported operand type(s) for /: 'str' and 'int'
True True

else and finally

A try statement has two more optional parts:

  • else runs only if the try block finished without an exception. Put the code that depends on success there, outside the protected area.
  • finally runs always: after success, after a handled exception, and even when an unhandled exception is passing through or the function returns. It is for clean-up that must happen no matter what, such as closing a connection. (For files, the with statement in the next lesson does this for you.)
PythonThe full try statement
1def divide(a, b):
2 try:
3 result = a / b
4 except ZeroDivisionError:
5 print(" cannot divide by zero")
6 return None
7 else:
8 print(" success")
9 return result
10 finally:
11 print(" finished dividing", a, "by", b)
12
13print(divide(9, 3))
14print(divide(1, 0))
Output
  success
  finished dividing 9 by 3
3.0
  cannot divide by zero
  finished dividing 1 by 0
None

Ask permission or ask forgiveness?

There are two styles for dealing with operations that might fail:

  • Look before you leap (LBYL): check first, then act. if key in d: value = d[key].
  • Easier to ask forgiveness than permission (EAFP): try it and handle the failure. try: value = d[key] except KeyError: ....

Python code leans towards EAFP, especially when the check would duplicate the work of the operation itself. Deciding whether a string is a valid number, for example, is easiest by simply calling int(). LBYL remains the clearer choice for simple, cheap checks such as an empty list.

PythonBoth styles
1settings = {"theme": "dark"}
2
3if "font" in settings:
4 font = settings["font"]
5else:
6 font = "default"
7print(font)
8
9try:
10 theme = settings["theme"]
11except KeyError:
12 theme = "light"
13print(theme)
14
15def is_number(text):
16 try:
17 float(text)
18 return True
19 except ValueError:
20 return False
21
22print(is_number("3.5"), is_number("1e3"), is_number("abc"))
Output
default
dark
True True False

Raising exceptions yourself

Your own functions should report invalid input the same way Python does: with raise. A function that receives an impossible value (a negative quantity, an unknown currency) should not print a message and return something odd; it should raise an exception with a clear message and let the caller decide what to do.

Choose a built-in type that fits: ValueError for a bad value, TypeError for the wrong type. Inside an except block, a bare raise re-raises the exception you just caught, which is useful when you want to log something and still let the error propagate.

PythonValidating arguments with raise
1def withdraw(balance, amount):
2 if amount <= 0:
3 raise ValueError("amount must be positive")
4 if amount > balance:
5 raise ValueError(f"insufficient funds: balance is {balance}")
6 return balance - amount
7
8print(withdraw(100, 30))
9for amount in (-5, 500):
10 try:
11 withdraw(100, amount)
12 except ValueError as error:
13 print("Refused:", error)
Output
70
Refused: amount must be positive
Refused: insufficient funds: balance is 100

Defining your own exception types

For errors that belong to your program's own rules, define a custom exception class. One line is enough: a class that inherits from Exception (or a more specific built-in). Callers can then catch exactly that error and nothing else. Classes are covered properly later; here, treat the line as a recipe.

PythonA custom exception
1class OutOfStockError(Exception):
2 pass
3
4stock = {"pen": 3, "ink": 0}
5
6def take(item):
7 if stock.get(item, 0) == 0:
8 raise OutOfStockError(f"{item} is out of stock")
9 stock[item] -= 1
10 return item
11
12for item in ["pen", "ink", "pad"]:
13 try:
14 print("Took", take(item))
15 except OutOfStockError as error:
16 print("Sorry:", error)
Output
Took pen
Sorry: ink is out of stock
Sorry: pad is out of stock

Pattern: asking until the input is valid

Interactive programs combine a loop with exception handling: keep asking until the answer converts, then break out. In a real program the loop body calls input(); the example feeds a list of typed answers instead, so it can run here.

while True:
    text = input("How many tickets? ")
    try:
        count = int(text)
    except ValueError:
        print("Please type a whole number.")
        continue
    if count < 1:
        print("At least one ticket.")
        continue
    break
PythonThe same loop with simulated answers
1answers = iter(["two", "-1", "3"])
2
3while True:
4 text = next(answers)
5 print("User typed:", text)
6 try:
7 count = int(text)
8 except ValueError:
9 print(" Please type a whole number.")
10 continue
11 if count < 1:
12 print(" At least one ticket.")
13 continue
14 break
15print("Booking", count, "tickets")
Output
User typed: two
  Please type a whole number.
User typed: -1
  At least one ticket.
User typed: 3
Booking 3 tickets

Debugging habits

Handling exceptions keeps a program running; debugging finds out why it failed. A few habits save hours:

  1. Read the whole last line of the traceback and the line number above it before changing anything.
  2. Reproduce with the smallest input that still fails.
  3. Print intermediate values (an f-string with = such as print(f"{total=}") shows name and value) or step through with a debugger.
  4. Check assumptions with assert: assert len(rows) > 0, "no rows loaded" stops immediately with your message when an assumption is false. Use it for programmer errors, not for validating user input.
Best Practices (Do)
  • ✓Catch specific exceptions close to where you can do something useful about them.
  • ✓Give raised exceptions messages that say what was wrong and with which value.
  • ✓Let unexpected exceptions crash during development so you see them.
Common Pitfalls (Don't)
  • ✕Write except: pass, which hides every error, including your own bugs.
  • ✕Wrap a whole program in one giant try block.
  • ✕Use exceptions for ordinary control flow when a simple if is clearer.

Check your understanding

Suggested Quiz
Question 1 of 5

Which exception does `int("3.5")` raise?

Practice

Parse a list of typed numbers without crashing on the bad ones, then clean and aggregate a CSV export that contains unusable rows.

[Problem]

Safe Integer Parser

Reveal Solution & Analysis
Model Solution
[Problem]

Pandas-Style Sales Analysis

Reveal Solution & Analysis
Model Solution

Quick Syntax & Reference Sheet

Concept / RuleSyntax / Usage
Handle one errortry: n = int(text) except ValueError: n = 0
Get the messageexcept ValueError as error: print(error)
Several typesexcept (KeyError, IndexError):
Success / alwayselse: ... finally: ...
Raiseraise ValueError("amount must be positive")
Re-raise after loggingexcept Exception: log(...) raise
Custom exceptionclass OutOfStockError(Exception): pass

Common Pitfalls & Developer Mistakes

✕ Mistake 1:

Catching everything with a bare except

Why it happens: Real bugs (typos, wrong types) are hidden along with the expected error.

Correction: Name the exception: except ValueError:
✕ Mistake 2:

Putting too much code in the try block

Why it happens: You cannot tell which line failed, and you may catch an error from code you did not mean to protect.

Correction: Keep only the risky call in try; move the rest to else.
✕ Mistake 3:

Printing an error and returning a fake value

Why it happens: Callers continue with wrong data.

Correction: raise an exception and handle it where a decision can be made.
✕ Mistake 4:

Using assert to validate user input

Why it happens: Assertions can be switched off with python -O, and they are meant for programmer mistakes.

Correction: Use if ... raise ValueError(...).

Frequently Asked Questions

What is the difference between a syntax error and an exception?

A syntax error is found before the program starts, because the code is not valid Python, and nothing runs. An exception happens while the program runs, at the line that fails, and can be handled with try/except.

Should I check with if or use try/except?

Use try/except when the check would repeat the operation's own work (such as parsing a number) or when failure is rare. Use if for cheap, obvious conditions such as an empty list.

Does finally run if I return inside try?

Yes. finally runs on every way out of the try statement, including return, break and an unhandled exception.

Hands-On Practice Problems

Article Tags:#Python#Exceptions#try except#Error Handling#Debugging
Was this lesson clear and helpful?

Discussion (0)

Sign in to ask questions and reply.
Loading discussion…
Discuss in community