Python Exception Handling


Python Exception Handling Interview with follow-up questions

1. Can you explain what exception handling is in Python?

Exception handling is the mechanism for dealing with runtime errors without crashing the program. When something goes wrong, Python raises an exception that propagates up the call stack until a matching handler catches it — otherwise the program terminates with a traceback.

The full structure has four clauses:

try:
    data = parse(payload)        # code that may fail
except ValueError as exc:        # handle a specific type
    log.warning("bad payload: %s", exc)
else:
    process(data)                # runs only if no exception
finally:
    cleanup()                    # always runs (cleanup)

What interviewers expect you to get right:

  • Catch specific exceptions, not a bare except:. Bare/except Exception swallows bugs and even KeyboardInterrupt (bare does). Be precise.
  • else runs when the try succeeds; finally always runs, even on return or re-raise — ideal for releasing resources.
  • EAFP over LBYL — Python style favors "try it and handle failure" over checking conditions first.
  • 3.11+ exception groups let you raise/handle multiple errors at once via ExceptionGroup and except* — relevant for concurrent code.
  • Use contextlib.suppress(FileNotFoundError) when you genuinely want to ignore one specific error.
↑ Back to top

Follow-up 1

What is the difference between an error and an exception?

In Python, an error is a condition that causes the program to terminate abruptly and cannot be handled by the program itself. Examples of errors include syntax errors and logical errors. On the other hand, an exception is an event that occurs during the execution of a program that disrupts the normal flow of the program, but can be handled by the program itself. Exceptions are typically caused by external factors or unexpected conditions, such as invalid user input or network failures.

Follow-up 2

Can you give an example of an exception?

Sure! One example of an exception is the ZeroDivisionError exception. This exception occurs when you try to divide a number by zero, which is mathematically undefined. Here's an example code snippet that raises a ZeroDivisionError exception:

x = 10
y = 0
result = x / y

Follow-up 3

What are some common exceptions in Python?

There are many built-in exceptions in Python, some of the common ones include:

  • ZeroDivisionError: Raised when division or modulo operation is performed with zero as the divisor.
  • TypeError: Raised when an operation or function is applied to an object of an inappropriate type.
  • ValueError: Raised when a function receives an argument of the correct type but an inappropriate value.
  • FileNotFoundError: Raised when a file or directory is requested but cannot be found.
  • KeyError: Raised when a dictionary key is not found.
  • IndexError: Raised when a sequence subscript is out of range.
  • NameError: Raised when a local or global name is not found.
  • IOError: Raised when an input/output operation fails.
  • ImportError: Raised when an import statement fails to find the module definition.
  • AttributeError: Raised when an attribute reference or assignment fails.

These are just a few examples, there are many more exceptions available in Python.

Follow-up 4

How can we handle multiple exceptions in Python?

In Python, we can handle multiple exceptions using multiple except blocks or by using a single except block with multiple exception types. Here's an example that demonstrates both approaches:

try:
    # Code that may raise exceptions
    pass
except ValueError:
    # Handle ValueError
    pass
except ZeroDivisionError:
    # Handle ZeroDivisionError
    pass
except (TypeError, IndexError):
    # Handle TypeError and IndexError
    pass

2. What is the purpose of the 'try' and 'except' blocks in Python?

try/except lets you run code that might fail and handle the failure gracefully instead of crashing. Code that may raise goes in try; the recovery logic goes in except, matched by exception type.

try:
    value = int(user_input)
except ValueError:
    print("Please enter a number")

Points interviewers look for:

  • Catch the specific type, not a bare except:. Bare except hides bugs and even catches KeyboardInterrupt/SystemExit.
  • Multiple handlers, most specific first; or group types in a tuple:
  try:
      risky()
  except (KeyError, IndexError) as exc:   # one handler, several types
      log.error("lookup failed: %s", exc)
  except Exception as exc:                 # broad fallback, last
      log.exception("unexpected")
  • as exc binds the exception object so you can inspect or log it.
  • Pair with else (runs on success) and finally (always runs, for cleanup).
  • Don't use exceptions for normal control flow in hot loops where a cheap check suffices — though Python's EAFP style does prefer try/except over pre-checking in most cases.
↑ Back to top

Follow-up 1

Can you give an example of how to use 'try' and 'except'?

Sure! Here's an example:

try:
    num = int(input('Enter a number: '))
    result = 10 / num
    print('The result is:', result)
except ZeroDivisionError:
    print('Error: Cannot divide by zero')
except ValueError:
    print('Error: Invalid input')

Follow-up 2

What happens if an exception is not caught in the 'except' block?

If an exception is not caught in the 'except' block, it will propagate up the call stack until it is caught by an outer 'try' block or until it reaches the top level of the program. If the exception is not caught at all, the program will terminate and an error message will be displayed.

Follow-up 3

Can we have multiple 'except' blocks for a single 'try' block?

Yes, we can have multiple 'except' blocks for a single 'try' block. Each 'except' block can handle a specific type of exception. If an exception occurs, Python will check each 'except' block in order and execute the code in the first 'except' block that matches the type of the exception. If none of the 'except' blocks match, the exception will propagate up the call stack.

3. What is the 'finally' keyword in Python and when is it used?

finally defines a block that always runs when leaving a try statement — whether it completed normally, raised an exception, or exited early via return/break/continue. It's the place for cleanup that must happen no matter what: closing files, releasing locks, rolling back transactions.

def read_config(path: str) -> str:
    f = open(path, encoding="utf-8")
    try:
        return f.read()          # even this return runs finally first
    finally:
        f.close()                # guaranteed cleanup

Things interviewers like to probe:

  • finally runs even after a return in the try or except block — and if finally itself returns, it overrides that earlier return (a classic gotcha; avoid returning from finally).
  • For resource cleanup, a with statement (context manager) is usually cleaner than manual try/finallywith open(...) as f: handles close() for you.
  • finally differs from else: else runs only when no exception occurred; finally runs unconditionally.
  • If an exception is in flight and finally doesn't re-raise or return, the original exception continues to propagate after finally completes.
↑ Back to top

Follow-up 1

Can you give an example of how to use 'finally'?

Sure! Here's an example:

try:
    # Some code that may raise an exception
    ...
except SomeException:
    # Exception handling code
    ...
finally:
    # Code that will always be executed
    # This could be closing a file, releasing resources, etc.
    ...

In this example, the code inside the 'finally' block will always be executed, regardless of whether an exception is raised or not.

Follow-up 2

What is the difference between 'else' and 'finally' in exception handling?

In exception handling, the 'else' block is executed only if no exception is raised in the try block, whereas the 'finally' block is always executed, regardless of whether an exception is raised or not. The 'else' block is used to specify code that should be executed if the try block completes successfully, without any exceptions being raised. On the other hand, the 'finally' block is used to specify code that should be executed regardless of the outcome of the try block, whether an exception is raised or not.

Follow-up 3

Does the 'finally' block always execute?

Yes, the 'finally' block always executes, regardless of whether an exception is raised or not. Even if an exception is raised and caught in the except block, the code inside the 'finally' block will still be executed. This makes the 'finally' block useful for performing cleanup operations or releasing resources that need to be done regardless of the outcome of the try-except block.

4. How can we raise our own exceptions in Python?

You raise an exception with the raise statement, passing an exception instance (or class) with an optional message:

raise ValueError("Invalid input")

This stops normal execution and propagates up the call stack until a handler catches it.

For domain errors, define custom exceptions by subclassing Exception (never BaseException directly):

class InsufficientFundsError(Exception):
    def __init__(self, requested: float, available: float) -> None:
        super().__init__(f"need {requested}, have {available}")
        self.requested = requested
        self.available = available

raise InsufficientFundsError(100, 30)

What interviewers want to hear:

  • Raise specific, meaningful types — a custom exception lets callers catch exactly your error without swallowing unrelated ones.
  • Exception chaining with raise ... from preserves context when re-raising:
  try:
      data = json.loads(raw)
  except json.JSONDecodeError as exc:
      raise ConfigError("bad config file") from exc

Use from None to deliberately suppress the original cause.

  • Bare raise inside an except re-raises the current exception, keeping its original traceback.
  • Subclass a shared base (e.g. class AppError(Exception)) so callers can catch a whole family at once.
↑ Back to top

Follow-up 1

What is the purpose of raising exceptions?

The purpose of raising exceptions is to handle exceptional situations in our code. Exceptions allow us to indicate that something unexpected or erroneous has occurred during the execution of our program.

By raising exceptions, we can interrupt the normal flow of execution and transfer control to an exception handler. This allows us to gracefully handle errors, provide error messages, and take appropriate actions to recover from the exceptional situation.

Follow-up 2

Can you give an example of a custom exception?

Yes, we can define our own custom exceptions by creating a new class that inherits from the built-in Exception class or any of its subclasses.

Here is an example of a custom exception named CustomException:

class CustomException(Exception):
    pass

We can then raise this custom exception using the raise statement, just like any other exception.

raise CustomException('This is a custom exception')

By defining custom exceptions, we can create more specific and meaningful exceptions that are tailored to our application's needs.

Follow-up 3

How can we pass arguments to a custom exception?

To pass arguments to a custom exception, we can define an __init__ method in our custom exception class. This method will be called when the exception is raised, allowing us to initialize the exception object with the desired arguments.

Here is an example of a custom exception named CustomException that accepts an argument:

class CustomException(Exception):
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

# Raise the custom exception with arguments
raise CustomException('Argument 1', 'Argument 2')

In this example, the CustomException class has an __init__ method that takes two arguments (arg1 and arg2). When the exception is raised, we pass the desired values for these arguments, which can then be accessed within the exception handler.

5. What is the use of the 'assert' statement in Python?

assert checks that a condition you believe is always true actually holds, raising AssertionError (with an optional message) if it doesn't. It's a tool for catching programmer errors and documenting invariants during development.

def average(values: list[float]) -> float:
    assert values, "values must not be empty"
    return sum(values) / len(values)

The gotcha interviewers almost always test: asserts are stripped when Python runs with -O (optimized mode). So:

  • Never use assert for runtime validation of user input, API arguments, or security checks — in optimized builds the check vanishes and the bad path runs. Use an explicit if ... raise ValueError(...) for that.
  • Use assert for internal invariants, sanity checks, and tests (pytest builds its rich introspection on the assert statement).
  • assert (cond, "msg") with a tuple is always truthy — a common bug. The correct form is assert cond, "msg" (no parentheses around both).

In short: assert documents "this should never happen" assumptions; raise real exceptions for "this might happen" conditions.

↑ Back to top

Follow-up 1

Can you give an example of how to use 'assert'?

Certainly! Here's an example:

x = 5
assert x > 0, 'x should be greater than 0'
print('x is positive')

Follow-up 2

What happens when an 'assert' statement fails?

When an 'assert' statement fails, it raises an AssertionError. This exception can be caught and handled like any other exception in Python.

Follow-up 3

In what scenarios is it beneficial to use 'assert' over traditional exception handling?

The 'assert' statement is particularly useful during development and testing phases. It helps to catch logical errors and assumptions that should always be true. By using 'assert', you can quickly identify and fix issues in your code. However, it is important to note that 'assert' statements should not be used for error handling in production code, as they can be disabled with the -O or -OO command line switches.

Live mock interview

Mock interview: Python Exception Handling

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.