Skip to content
C

Exception Handling

Handling runtime errors gracefully with try/except/else/finally, catching multiple exception types, raising exceptions deliberately with raise, and defining custom exception classes.


No matter how carefully you write code, things go wrong at runtime — a user enters text instead of a number, a file doesn't exist, the internet connection drops. Exception handling is how Python lets your program deal with these problems gracefully, instead of crashing.


1. What are Errors and Exceptions?

What is it?

  • An error is a general term for anything that goes wrong in a program.
  • An exception is a specific type of error that occurs during program execution (at runtime) — Python detects it and, if unhandled, stops the program and shows a traceback message.
Definition: An exception is an event that disrupts the normal flow of a program's execution, usually caused by an invalid operation.

Why do we use exception handling?

Without it, a single unexpected input (like text where a number was expected) would crash the entire program. Exception handling lets you anticipate possible problems and respond to them sensibly — showing a friendly message, retrying, or logging the issue — instead of the whole program stopping abruptly.

Simple Example — An Unhandled Exception

python
age = int(input("Enter your age: ")) print(f"You are {age} years old")

If the user types "abc" instead of a number:

ValueError: invalid literal for int() with base 10: 'abc'

The program crashes completely at this point — nothing after this line runs.

Common Built-in Exception Types

ExceptionWhen it Occurs
ValueErrorInvalid value for an operation (e.g., int("abc"))
TypeErrorWrong data type used in an operation
ZeroDivisionErrorDividing by zero
FileNotFoundErrorTrying to open a file that doesn't exist
KeyErrorAccessing a dictionary key that doesn't exist
IndexErrorAccessing a list index that's out of range
NameErrorUsing a variable that hasn't been defined
AttributeErrorCalling a method/attribute that doesn't exist on an object

2. try and except

What is it?

try lets you "attempt" a risky piece of code. If an exception occurs, Python jumps to the matching except block instead of crashing the whole program.

Syntax

python
try: # risky code except ExceptionType: # code that runs if that exception occurs

Simple Example

python
try: age = int(input("Enter your age: ")) print(f"You are {age} years old") except ValueError: print("That's not a valid number. Please enter digits only.")

Sample Interaction:

Enter your age: abc
That's not a valid number. Please enter digits only.

Explanation of the Code

  • Python attempts everything inside try.
  • If a ValueError occurs (like converting "abc" to int), Python immediately jumps to the matching except ValueError: block instead of crashing.
  • The program continues running normally after the except block — it doesn't stop.

Handling Multiple Exception Types

python
try: num1 = int(input("Enter first number: ")) num2 = int(input("Enter second number: ")) print(num1 / num2) except ValueError: print("Please enter valid numbers.") except ZeroDivisionError: print("You cannot divide by zero.")

Catching Any Exception (Use Sparingly)

python
try: risky_operation() except Exception as e: print("Something went wrong:", e)

Explanation: except Exception as e catches almost any error and stores its details in e. This is useful as a safety net, but catching specific exceptions (like ValueError, ZeroDivisionError) is better practice, since it doesn't accidentally hide unrelated bugs.

Real-World Example

A banking app should never crash just because a user typed letters into an amount field — it should catch the error and ask them to re-enter a valid number.

Common Mistakes

  • Using a bare except: (with no exception type at all), which catches literally everything — including mistakes you'd actually want to notice, like typos in your own code. Always specify the exception type when possible.
  • Wrapping too much code inside one try block, making it unclear exactly which line caused the problem.

Important Points

  • try/except prevents a single error from crashing the entire program.
  • You can have multiple except blocks for different exception types.
  • Catching specific exceptions is better practice than catching everything blindly.

Practice

  1. Write a program that asks the user for two numbers and divides them, handling both ValueError and ZeroDivisionError.

3. else with try

What is it?

An optional else block that runs only if no exception occurred in the try block.

Syntax

python
try: # risky code except ExceptionType: # runs if an exception occurred else: # runs only if NO exception occurred

Simple Example

python
try: num = int(input("Enter a number: ")) except ValueError: print("Invalid input.") else: print(f"You entered {num}, and it's a valid number!")

Explanation

  • The else block is a clean way to separate "the risky part" (inside try) from "what happens next, only on success" (inside else) — keeping the code that could fail isolated from the code that depends on it succeeding.

Important Points

  • else only runs if the try block completes with no exception.
  • Helps keep code organized: risky logic in try, success-path logic in else.

4. finally

What is it?

A finally block always runs — whether an exception occurred or not, and even if the exception wasn't caught at all. It's typically used for cleanup actions, like closing a file or a database connection.

Syntax

python
try: # risky code except ExceptionType: # handle exception finally: # this ALWAYS runs, no matter what

Simple Example

python
try: file = open("data.txt", "r") content = file.read() print(content) except FileNotFoundError: print("File not found.") finally: print("Attempted to read the file — cleanup complete.")

Explanation

  • Whether the file is found or not, the finally block always executes — this is why finally is the classic place to close files or database connections, ensuring cleanup happens no matter what.
  • (In modern Python, with open(...) handles file closing automatically, but finally is still essential for other types of cleanup, like closing network connections.)

Common Mistakes

  • Assuming finally only runs on success — it actually runs in every case, including when an exception is raised and not even caught.

Important Points

  • finally always executes — success, failure, or even an uncaught exception.
  • Commonly used for guaranteed cleanup: closing files, releasing resources, disconnecting from a database.

Comparison Table — try / except / else / finally

BlockRuns When
tryAlways attempted first
exceptOnly if a matching exception occurs
elseOnly if NO exception occurred
finallyALWAYS — regardless of success or failure

5. Raising Exceptions with raise

What is it?

raise lets you deliberately trigger an exception yourself — useful when your own code detects an invalid situation that should stop normal execution.

Simple Example

python
def withdraw(balance, amount): if amount > balance: raise ValueError("Insufficient balance for this withdrawal") return balance - amount try: new_balance = withdraw(1000, 1500) except ValueError as e: print("Error:", e)

Output:

Error: Insufficient balance for this withdrawal

Explanation of the Code

  • Instead of letting the program continue with an invalid state (a negative balance), raise deliberately stops execution of the withdraw() function and hands control to the nearest matching except.
  • The custom message "Insufficient balance for this withdrawal" explains exactly what went wrong, which is far more helpful than a generic crash.

Real-World Example

A form validation function might raise a ValueError if a required field is empty, or if an age entered is negative — deliberately flagging a problem the program itself knows to check for.

Important Points

  • raise can be used with any built-in exception type, or a custom one (see below).
  • Raising exceptions with clear messages makes debugging far easier.

Practice

  1. Write a function set_age(age) that raises a ValueError if age is negative, and test it with a try/except.

6. Custom Exceptions

What is it?

You can define your own exception types by creating a class that inherits from Python's built-in Exception class — useful when built-in exceptions don't clearly describe your specific problem.

Simple Example

python
class InsufficientBalanceError(Exception): pass def withdraw(balance, amount): if amount > balance: raise InsufficientBalanceError("Withdrawal amount exceeds available balance") return balance - amount try: withdraw(1000, 1500) except InsufficientBalanceError as e: print("Transaction failed:", e)

Output:

Transaction failed: Withdrawal amount exceeds available balance

Explanation of the Code

  • class InsufficientBalanceError(Exception): creates a brand-new exception type, specific to this application's needs.
  • pass means the class doesn't need any extra code — it inherits everything it needs from Exception.
  • This custom exception can now be raised and caught just like any built-in one, but its name makes the actual problem immediately clear to anyone reading the code.

Real-World Example

Large applications commonly define custom exceptions like InvalidLoginError, OutOfStockError, or PaymentDeclinedError — these communicate the exact business problem far more clearly than a generic ValueError would.

Common Mistakes

  • Forgetting to inherit from Exception (or one of its subclasses) when creating a custom exception class.
  • Creating overly specific custom exceptions for situations a built-in exception already covers well.

Important Points

  • Custom exceptions must inherit from Exception (directly or indirectly).
  • They make error handling far more readable and meaningful in larger applications.

Practice

  1. Create a custom exception NegativeValueError and use it in a function that calculates the square root of a number, raising the exception if the number is negative.

Common Beginner Mistakes — Summary for This Section

  • Using a bare except: that catches everything, hiding real bugs.
  • Wrapping too much code in one try block, making it hard to know what actually failed.
  • Forgetting that finally always runs, even after an exception.
  • Not providing a clear message when raising an exception.

Cheat Sheet — Exception Handling

python
try: risky_code() except ValueError: handle_value_error() except (TypeError, KeyError) as e: handle_multiple_types(e) except Exception as e: handle_anything_else(e) else: only_runs_if_no_exception() finally: always_runs() raise ValueError("custom message") class MyCustomError(Exception): pass

Interview Questions

Q1. What is the difference between an error and an exception? Answer: "Error" is a general term for anything going wrong. An exception is specifically a runtime event Python detects and can be caught and handled using try/except, preventing a crash.

Q2. What is the purpose of the `finally` block? Answer: Code inside finally always runs, regardless of whether an exception occurred or was handled — typically used for guaranteed cleanup actions like closing files or connections.

Q3. When does the `else` block (paired with `try`) execute? Answer: Only when the try block completes successfully with no exception raised.

Q4. Why is using a bare `except:` considered bad practice? Answer: It catches every possible exception, including ones you didn't anticipate, which can hide genuine bugs and make debugging much harder. Catching specific exception types is safer and clearer.

Q5. How do you create a custom exception in Python? Answer: By defining a class that inherits from Exception (or one of its subclasses), then using raise to trigger it when needed.

Q6. What exception is raised when dividing a number by zero? Answer: ZeroDivisionError.


Practice Questions

Beginner

  1. Write a program that asks for a number and handles the case where the user enters non-numeric text.
  2. Write a program that divides two numbers and handles division by zero.
  3. Write a program that tries to open a file that may not exist, and prints a friendly message if it isn't found.
  4. Write a program that accesses a dictionary key that might not exist, using try/except instead of .get().
  5. Write a program with a finally block that always prints "Program finished" no matter what.

Intermediate

  1. Write a function that raises a ValueError if a given age is negative, and handle it in a try/except block.
  2. Write a program that handles multiple exception types (ValueError, ZeroDivisionError, IndexError) separately with different messages.
  3. Create a custom exception InvalidAgeError and use it to validate a user's age input.
  4. Write a program that keeps asking the user for a valid number until they provide one, using a loop combined with try/except.
  5. Write a function that reads a file and raises a custom EmptyFileError if the file exists but is empty.

Challenge

  1. Build a simple calculator that handles invalid input, division by zero, and invalid operators, each with a clear, specific error message.
  2. Create a custom exception hierarchy: a base AppError exception, with two subclasses ValidationError and AuthenticationError, and demonstrate catching each specifically as well as catching the base type generically.
  3. Write a program that validates a list of user-submitted ages, collecting all invalid ones into an error report instead of stopping at the first invalid value.

Mock Test

  • Exception Handling - Quick Test

    10 questions covering try/except/else/finally, raise, and custom exceptions.

    10 questions · 10 min · Easy
    Start Mock Test

Coding Problems