Try-except in Python is a built-in safety net that lets your program survive runtime errors instead of crashing. You wrap risky code inside a try block, and if Python hits an error, it jumps to your except block where you decide what to do next. The most common errors students need to handle with try-except are IndexError, KeyError, TypeError, ValueError, AttributeError, NameError, ZeroDivisionError, FileNotFoundError, and ImportError. Indentation errors are a separate case because they happen before the program even runs.
Here is the smallest example of try-except in action:
try:
age = int(input("Enter your age: "))
print(f"You are {age} years old")
except ValueError:
print("That was not a valid number")
Without the try-except, typing “hello” instead of a number would crash the whole program. With it, the user gets a friendly message and your program keeps running. That single concept is the foundation of every robust Python application you will ever write. If you are stuck on a Python assignment where errors keep stopping your code, our Python tutors can help you debug it and explain the fix in plain English.
This guide walks you through how try-except works, then shows you the 10 most common errors students hit and exactly how to handle each one. By the end, you will know which errors deserve a try-except and which ones you should fix at the source instead.
How Try-Except Actually Works
Most beginners learn try-except as a magic spell that prevents crashes. That is half true. The full picture is more useful.
When Python runs your code, it goes line by line. The moment something goes wrong (a missing file, a bad conversion, an empty list access), Python creates an exception object that contains details about what happened. Without a try-except, this exception bubbles up until it hits the top of your program and Python prints a traceback before quitting.
A try-except intercepts that exception before it can bubble up.
The full syntax has four parts. Most students only know two:
try:
# Code that might fail
risky_thing()
except SomeError:
# Runs only if SomeError happens in try
handle_it()
else:
# Runs only if no error happened
celebrate()
finally:
# Runs no matter what
clean_up()
The try and except are the only required parts. The else runs when everything went smoothly. The finally runs whether things broke or not, which makes it perfect for cleanup work like closing files.
Here is a real example that uses all four:
try:
file = open("report.txt", "r")
content = file.read()
except FileNotFoundError:
print("Report file is missing")
else:
print(f"Read {len(content)} characters successfully")
finally:
print("Done attempting to read the file")
If the file does not exist, you see the missing-file message and then the “done” message. If the file exists and reads fine, you see the success message and then the “done” message. The finally block always runs, which is why it is the right place for “make sure this happens no matter what” code.
One detail catches many students off guard. The except clause needs to name what it is catching. Writing a bare except: with nothing after it catches every possible error, including ones you never intended to handle. That sounds convenient but it actively hides bugs. We will get to why later.
The 10 Most Common Errors and How to Handle Each One
Try-except is the tool. The errors below are what you point it at. Each one is a real exception students hit in homework, projects, and competitive programming. For each error, you will see what causes it, a minimum example that triggers the crash, and the try-except pattern that handles it gracefully.
1. IndexError: List Index Out of Range
This is the first runtime error most Python beginners meet. You ask for an item at a position that does not exist in your list.
scores = [85, 90, 78]
print(scores[5])
Lists in Python use zero-based indexing. A list of three items has positions 0, 1, and 2. Asking for position 5 throws an IndexError because nothing lives there.
The try-except version:
scores = [85, 90, 78]
try:
print(scores[5])
except IndexError:
print("That position does not exist in the list")
Honestly though, the better fix here is to check the length first:
if 5 < len(scores):
print(scores[5])
else:
print("Not enough scores in the list")
Try-except is for unexpected failures. If you can prevent the error with a quick if check, do that. Save try-except for the cases where you genuinely cannot predict the failure.
2. KeyError: That Key Is Not in the Dictionary
The dictionary equivalent of IndexError. You try to read a key that the dictionary does not contain.
student = {"name": "Priya", "grade": "A"}
print(student["age"])
Python checks the dictionary, finds no age key, and raises a KeyError.
Handling with try-except:
try:
print(student["age"])
except KeyError:
print("Age was not recorded for this student")
But there is a cleaner native Python way that does not need try-except at all. The .get() method returns a default if the key is missing:
print(student.get("age", "Not recorded"))
Use try-except for KeyError only when missing data is a real problem that needs a response. For optional data, .get() is cleaner.
3. TypeError: That Operation Does Not Work on These Types
Python is strict about types when it comes to operations. Adding a string to a number, calling a method on the wrong object, or passing the wrong number of arguments to a function all trigger TypeError.
total = "Price: " + 100
You cannot add a string and an integer directly in Python. The fix at the source is type conversion:
total = "Price: " + str(100)
When the data comes from outside (a file, an API, user input), you might not know what type you will get. That is when try-except helps:
A useful trick when debugging TypeError is to print type(variable) right before the failing line. Half the time the bug is that you thought a variable was an int but it is actually a str from input().
4. ValueError: Right Type, Wrong Content
This one trips up students because the error message sounds vague. ValueError means the type is correct, but the actual value does not make sense for the operation.
number = int("hello")
int() expects a string that looks like a number. The string “hello” is the right type (a string) but the wrong content. So ValueError, not TypeError.
This is the textbook case for try-except because user input is unpredictable:
user_input = input("Enter a number: ")
try:
number = int(user_input)
print(f"Twice your number is {number * 2}")
except ValueError:
print("That was not a number, please try again")
You should reach for try-except on every conversion of unknown input. Trying to validate the string manually with checks like isdigit() quickly gets messy when you allow negatives, decimals, or scientific notation. Let Python’s parser do the work and catch the failure.
5. AttributeError: This Object Does Not Have That Method
This shows up when you call a method that does not exist on the object you have. Often it is because you confused two similar types, or you misspelled a method name.
numbers = [1, 2, 3]
numbers.push(4)
Python lists do not have a push method. That is JavaScript. Python lists use append. So AttributeError: 'list' object has no attribute 'push'.
numbers.append(4)
Try-except is rarely the right tool for AttributeError because this is almost always a code bug, not an external condition. The exception is when you are dealing with optional attributes on flexible objects:
try:
user.send_notification()
except AttributeError:
print("This user account does not support notifications")
When you see AttributeError, first ask: did I misspell something? Run dir(my_object) in the Python shell to see every method available. Most AttributeError problems disappear in seconds with this trick.
6. NameError: Python Has Never Heard of That Variable
You used a variable or function before defining it, or you misspelled its name.
print(total)
total = 100
Python reads top to bottom. At the line print(total), the variable does not exist yet, so NameError.
total = 100
print(total)
NameError is almost never something try-except should catch. It is a structural problem in your code: variable used before assignment, typo in the name, or forgetting to import something. Fix it at the source.
try:
print(total)
except NameError:
total = 0
print(total)
The code above technically “works” but it hides the real problem. If total was supposed to be defined earlier, you have a bigger bug that the try-except is masking.
7. IndentationError: Python Cannot Even Run This
IndentationError is special because try-except cannot catch it. Indentation errors happen at the parse stage, before any of your code runs. There is no opportunity to wrap it in a try because Python never gets that far.
def greet():
print("Hello")
The print line is not indented under the function, so Python rejects the entire file with an IndentationError.
The fix is always at the source. Pick four spaces per indent level (the standard), never mix tabs and spaces, and use an editor like VS Code or PyCharm that shows indentation guides. If you copy code from the web and it breaks, the most common cause is hidden tab characters.
So why mention it in a try-except guide? Because students often ask “why does my try-except not catch this?” The answer is that IndentationError, SyntaxError, and any other parse-time error live outside the try-except world entirely. They are not exceptions in the same sense.
8. ImportError and ModuleNotFoundError: Cannot Find What You Asked For
These two are closely related. ModuleNotFoundError is a specific subtype of ImportError. Both happen when Python cannot locate a module you tried to import.
import maths
The module is called math, not maths. So ModuleNotFoundError.
Two common causes:
- Typo in the name. Easy fix, just correct the spelling.
- Package not installed. For external packages like
requests,pandas, ornumpy, you need to install them withpip install requestsbefore importing.
Try-except can be useful here when you want your program to work with or without an optional dependency:
try:
import pandas as pd
use_pandas = True
except ImportError:
use_pandas = False
print("Pandas not available, using basic mode")
For your own code where the import should always work, do not wrap it. Just fix the import.
9. ZeroDivisionError: Math Does Not Allow This
You divided a number by zero, which is mathematically undefined. Python raises ZeroDivisionError.
total = 100
count = 0
average = total / count
The straightforward fix is to check the divisor first:
if count != 0:
average = total / count
else:
average = 0
Try-except works too, especially when the divisor comes from a calculation you cannot easily predict:
try:
average = total / count
except ZeroDivisionError:
average = 0
print("No items to average")
A subtle point: in Python 3, 1 / 0 and 1 // 0 both raise ZeroDivisionError. So does 1 % 0. Float division by zero also raises it, unlike some other languages where you would get inf or NaN.
10. FileNotFoundError: That File Is Not Where You Said
You tried to open a file that does not exist at the path you gave.
file = open("data.txt", "r")
If data.txt is not in the current working directory, you get FileNotFoundError.
File operations are the textbook use case for try-except because the filesystem is outside your control:
try:
with open("data.txt", "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print("Could not find data.txt, please check the file path")
Notice the with statement. It ensures the file gets closed automatically even if an error happens during reading. Combining with and try-except is the safest way to handle files in Python.
One common confusion: FileNotFoundError only fires when the file is missing. If the file exists but you do not have permission to read it, you get PermissionError instead. If the path is malformed, you might get OSError. To handle all file-related issues, you can catch the parent class:
try:
with open("data.txt", "r") as file:
content = file.read()
except OSError as e:
print(f"Could not open the file: {e}")
OSError is the parent of both FileNotFoundError and PermissionError, so it catches both.
Best Practices That Separate Pros from Beginners
You now know the syntax and the 10 errors. The difference between a beginner using try-except and a pro using it comes down to discipline. Here are the habits worth building, framed as pairs of what to do and what to avoid.
Be specific, never bare.
# Avoid
try:
risky_operation()
except:
pass
# Prefer
try:
risky_operation()
except ValueError:
print("Invalid value")
A bare except catches everything, including KeyboardInterrupt (when the user hits Ctrl+C) and SystemExit. You almost never want to swallow those.
Catch what you can handle, let the rest bubble up.
If you do not know how to recover from an error, do not catch it. Let it crash, see the traceback, and fix the real bug. Catching an exception just to print “something went wrong” is worse than letting the program crash because the traceback would have told you exactly what was wrong.
Keep the try block small.
# Avoid: too much inside try
try:
data = load_data()
cleaned = clean_data(data)
save_results(cleaned)
send_email_summary()
except Exception:
print("Something failed")
# Prefer: each risky step gets its own scope
try:
data = load_data()
except FileNotFoundError:
print("Data file missing")
return
cleaned = clean_data(data)
save_results(cleaned)
send_email_summary()
Smaller try blocks tell you exactly what failed and let you respond differently to different failures.
Log the exception, do not just print.
For real projects, use the logging module instead of print. It gives you timestamps, severity levels, and the ability to write to files.
import logging
try:
process_order(order_id)
except KeyError as e:
logging.error(f"Missing field in order {order_id}: {e}")
The as e part captures the actual exception object so you can read its details.
Use else for the success path.
When you have code that should run only if the try succeeded, put it in else rather than continuing inside the try. This keeps the try block focused on just the risky operation.
try:
user = fetch_user(user_id)
except UserNotFoundError:
return None
else:
return user.profile
Use finally for cleanup that must happen.
If you open a file, start a network connection, or grab a lock, put the release code in finally. Better yet, use with statements where available since they handle cleanup automatically.
When Try-Except Is the Wrong Answer
Try-except is a tool, not a default mode. There are cases where reaching for it actually makes your code worse.
Do not use try-except as flow control.
Some beginners use exceptions to handle expected conditions:
# Avoid this
try:
value = my_dict["key"]
except KeyError:
value = "default"
# Use this instead
value = my_dict.get("key", "default")
Exceptions are slower than normal code paths because Python has to build the exception object and unwind the stack. For predictable conditions like “this key might be missing,” use the language feature designed for it.
Do not use try-except to hide bugs you should fix.
If your code throws NameError because you misspelled a variable, the fix is to correct the spelling, not to wrap it in try-except and pretend everything is fine. Try-except should handle situations outside your control, not lazy code.
Do not use try-except when fail-fast is better.
In scripts that run during deployment or testing, you often want errors to crash loudly so you notice them. Silently catching errors in this context can let bad data slip into production. Sometimes the right behavior is to let the program die and see the traceback.
Do not catch the parent exception class without thinking.
# Catches everything, including bugs you should know about
try:
do_something()
except Exception:
pass
Exception is the parent of almost all built-in exceptions. Catching it is almost the same as a bare except. Be specific about what you expect.
A useful question to ask before adding try-except: “If this fails, what should the program do that is different from crashing?” If the honest answer is “nothing, I just do not want to see the error message,” then you do not need try-except. You need to fix the underlying issue.
Frequently Asked Questions
Can try-except catch every type of Python error?
No. Try-except can only catch runtime exceptions, which are errors that happen while your code is running. It cannot catch SyntaxError or IndentationError because those happen before your code starts running. You have to fix those in the source code itself.
Should I always use try-except with user input?
Yes for type conversions like int(input()) and float(input()), where invalid input is common. For other inputs, validation with if checks is often cleaner. The general rule is: use try-except when the failure is unpredictable, use if checks when the failure is predictable.
What is the difference between except Exception and bare except?
A bare except: catches absolutely everything, including system exits and keyboard interrupts. except Exception: catches all normal errors but lets system-level signals through, which is usually what you want. Both are too broad for most cases. Catch specific exceptions whenever possible.
Can I have multiple except blocks for one try?
Yes, and you should when different errors need different responses. List them in order from most specific to least specific. Python checks each except in order and runs the first one that matches.
try:
do_risky_thing()
except ValueError:
print("Bad value")
except TypeError:
print("Bad type")
except Exception:
print("Something else went wrong")
How do I get the actual error message inside except?
Use the as keyword to capture the exception object:
try:
int("abc")
except ValueError as e:
print(f"Error details: {e}")
The variable e holds the exception, and printing it shows the message Python would have displayed.
Is try-except slow?
The try part is essentially free when nothing goes wrong. The cost comes when an exception is actually raised, because Python has to build the exception object and find a matching handler. For rare errors, this is fine. For predictable conditions that happen frequently, it can slow your code down.
Can I raise my own exceptions?
Yes, using raise. This is useful when you want to signal an error condition in your own functions:
You can also create custom exception classes by inheriting from Exception, which is common in larger projects.
What does else do that I cannot do at the end of try?
Code inside else runs only if no exception was raised in try. Code at the end of try runs too, but if it raises its own exception, your except will catch it. Using else keeps the “this should not be wrapped” code separate from the “this might fail” code.
Stuck debugging a Python error that just will not go away?
Our Python tutors have helped students untangle thousands of
KeyError,TypeError, andValueErrorcrashes in real assignments. If you want more hands-on practice with concepts like loops, dictionaries, and error handling, browse our Python project ideas for beginners and try a few. Building small things is the fastest way to internalize what try-except does.
