InterviewQAs

Python Exception Handling

Download as PDF
All questions in this page are included
Preparing…
Download PDF
PEH
Python Exception Handling

Python exception handling is critical for building resilient applications that can gracefully manage runtime errors. By anticipating potential failure points, developers can prevent crashes and maintain smooth user experiences.

The core of Python's exception handling revolves around try, except, else, and finally blocks. Each serves a specific purpose: 'try' wraps risky operations, 'except' catches and handles exceptions, 'else' executes if no exception occurs, and 'finally' runs regardless of outcome for cleanup.

Python supports a hierarchy of built-in exceptions like ValueError, KeyError, and IOError. Understanding this hierarchy allows developers to catch specific errors precisely while letting unexpected issues propagate for higher-level handling.

Creating custom exceptions is a common practice in large projects. It enables teams to signal domain-specific error conditions and maintain clear, maintainable error handling without conflating unrelated exception types.

Advanced exception handling techniques include context managers with the 'with' statement, chaining exceptions for debugging, and re-raising exceptions with additional context. Proper use of these patterns improves code robustness and simplifies troubleshooting in production systems.

Question 01

What is the difference between syntax errors and exceptions in Python, and how should each be handled?

EASY

Syntax errors occur when the Python interpreter encounters code that violates the language's grammar rules. These errors are detected at compile time, preventing the program from running until corrected.

Exceptions, on the other hand, are runtime errors that occur while the program is executing, such as division by zero or accessing a missing key in a dictionary. Unlike syntax errors, exceptions can be anticipated and handled using try-except blocks.

Proper handling involves fixing syntax errors directly in the code and using structured exception handling for runtime errors. This ensures programs remain operational even when unexpected conditions arise.

Question 02

Explain the role of the 'finally' block in Python exception handling with an example scenario.

MEDIUM

The 'finally' block in Python is used to execute code that must run regardless of whether an exception occurs or not. It is typically used for cleanup activities such as closing files, releasing network connections, or rolling back transactions.

For example, if a program opens a file for writing, a 'finally' block can ensure the file is closed properly even if an unexpected error occurs during writing. This prevents resource leaks and maintains system stability.

The 'finally' block is executed after the try and except blocks, and it runs whether an exception was caught or not. This makes it ideal for critical cleanup operations that should not be skipped under any circumstances.

Question 03

What are exception chaining and context preservation in Python, and why are they important in production code?

HARD

Exception chaining allows one exception to propagate while keeping track of the original exception that triggered it. This is done using the 'raise ... from ...' syntax, which helps maintain a clear error trail.

Context preservation ensures that even when an exception is handled or re-raised, the traceback contains information about the original failure. This is critical in production environments for debugging complex issues, especially in multi-layered systems.

Using exception chaining improves maintainability and debuggability by providing developers with full visibility into the root causes of errors without losing intermediate context, thereby preventing silent failures or misleading logs.

Question 04

Which of the following statements about Python exception handling are true?

EASY
  • A A try block can have multiple except clauses
  • B The else block executes only if an exception is caught
  • C The finally block always executes regardless of exceptions
  • D Python requires all exceptions to be explicitly caught

Python allows multiple except clauses to handle different types of exceptions separately. This enables precise error handling based on exception type.

The finally block is designed to always execute, whether an exception occurs or not, making it suitable for cleanup operations. The else block only executes if no exception occurs, and Python does not require all exceptions to be caught explicitly.

Question 05

When should you define a custom exception class in Python?

MEDIUM
  • A When you need to signal domain-specific errors clearly
  • B When handling simple built-in exceptions like ValueError
  • C When you want to provide additional context or attributes with the error
  • D When you want to bypass exception handling entirely

Custom exceptions allow developers to create clear and maintainable error signaling for specific business logic or domain requirements. They can also carry extra data, making it easier to handle errors intelligently.

Using custom exceptions for trivial built-in errors or to bypass handling does not provide practical benefits and can introduce unnecessary complexity.

Question 06

Consider the following scenario: a Python web service logs errors and retries operations. Which practices improve exception handling in this context?

HARD
  • A Using exception chaining to preserve original errors
  • B Catching all exceptions using a bare except clause
  • C Retrying only on expected transient exceptions
  • D Logging exceptions with full traceback information

Exception chaining allows developers to maintain a full error context, which is crucial for debugging and analysis in web services.

Retriable operations should only retry expected transient errors to prevent compounding failures. Logging complete tracebacks ensures visibility into the failure chain, while a bare except clause can hide critical issues and is considered bad practice.

Question 07

Write Python code that opens a file, reads its contents, and ensures the file is closed properly using exception handling.

EASY

This code uses a try block to read the file and an except block to catch a FileNotFoundError, providing a clear error message.

The finally block ensures that the file is closed regardless of whether an exception occurred, preventing resource leaks. Checking 'file' in locals() ensures we don't attempt to close a file that was never opened.

// Python
try:
    file = open('data.txt', 'r')
    content = file.read()
    print(content)
except FileNotFoundError as e:
    print(f'Error: {e}')
finally:
    if 'file' in locals():
        file.close()
Question 08

Implement a Python function that divides two numbers and raises a custom exception if division by zero is attempted.

MEDIUM

The code defines a custom exception 'DivisionError' to signal division-specific errors. This makes error handling more semantic and domain-specific.

The function checks the divisor and raises the custom exception if it is zero. Catching the exception allows the program to handle the error gracefully without crashing.

// Python
class DivisionError(Exception):
    def __init__(self, message):
        super().__init__(message)

def safe_divide(a, b):
    try:
        if b == 0:
            raise DivisionError('Cannot divide by zero')
        return a / b
    except DivisionError as e:
        print(f'Error: {e}')

print(safe_divide(10, 0))
Question 09

Write Python code that demonstrates exception chaining by catching a KeyError and raising a custom exception while preserving the original exception.

HARD

This code first attempts to access a missing dictionary key, which raises a KeyError. The except block catches it and raises a custom ConfigError using 'from' to chain the original exception.

Chaining preserves the original exception context, making debugging easier by showing both the cause and the higher-level error. This pattern is useful in layered applications where low-level errors need to be translated into domain-specific exceptions.

// Python
class ConfigError(Exception):
    pass

config = {}
try:
    value = config['timeout']
except KeyError as ke:
    raise ConfigError('Configuration key missing') from ke
Question 10

Demonstrate using a context manager with exception handling for opening a file in Python.

MEDIUM

Using the 'with' statement automatically manages the file resource, ensuring it is closed properly even if an exception occurs inside the block.

The try-except block outside the 'with' handles potential errors, such as the file not existing. This combination of context managers and exception handling is considered best practice for resource management in Python.

// Python
try:
    with open('log.txt', 'r') as log_file:
        for line in log_file:
            print(line.strip())
except FileNotFoundError as e:
    print(f'Error: {e}')
Question 11

Why is catching broad exceptions like Exception considered risky in large Python applications?

MEDIUM

Catching broad exceptions can unintentionally suppress critical failures that developers did not anticipate. For example, a database timeout, serialization bug, and programming error could all be swallowed by the same generic handler, making troubleshooting significantly harder.

In production systems, overly broad exception handling often leads to hidden corruption or inconsistent state because the application continues running after an unexpected failure. This is especially dangerous in financial, healthcare, or data-processing systems where silent failures can affect downstream operations.

A better approach is to catch only expected exceptions and allow unknown issues to propagate upward. This preserves visibility into real defects while still enabling graceful recovery for predictable operational problems.

Question 12

How should exception handling be designed in retry-based systems interacting with external APIs?

HARD

Retry-based systems should distinguish between transient failures and permanent failures. Temporary network interruptions, rate limits, or short-lived service outages are usually safe to retry, while authentication failures or malformed payloads are not.

Blind retries can amplify outages and overload already struggling services. Production-grade systems typically use exponential backoff, retry limits, jitter, and structured logging to avoid creating retry storms.

Exception handling should preserve enough context to support observability. This includes request identifiers, endpoint details, retry count, and original traceback information. Without this metadata, diagnosing intermittent failures becomes extremely difficult in distributed environments.

Question 13

What practical advantages do custom exceptions provide in enterprise Python applications?

EASY

Custom exceptions allow developers to represent business-specific failure conditions explicitly. Instead of relying on generic exceptions, applications can communicate intent clearly through meaningful exception names such as PaymentDeclinedError or InventorySyncError.

They also improve maintainability because different layers of the application can react differently to specific failure categories. For example, an API gateway might retry network-related exceptions but immediately reject validation-related exceptions.

In large teams, custom exceptions establish a shared vocabulary around failure handling. This reduces ambiguity during debugging, monitoring, and operational support.

Question 14

Which practices help improve debugging quality when handling exceptions in Python applications?

MEDIUM
  • A Logging complete traceback information
  • B Replacing all exceptions with generic error messages
  • C Adding contextual metadata before re-raising exceptions
  • D Suppressing exceptions to avoid application crashes

Complete traceback information provides visibility into where and why a failure occurred. Without stack traces, engineers often struggle to reproduce production incidents.

Adding contextual metadata such as user IDs, request IDs, or transaction references before re-raising exceptions significantly improves root-cause analysis. Suppressing exceptions or hiding details behind generic messages reduces operational visibility.

Question 15

Which statements about exception propagation in Python are correct?

HARD
  • A Unhandled exceptions propagate upward through the call stack
  • B finally blocks are skipped if an exception occurs
  • C raise without arguments can re-raise the current exception
  • D Exception chaining removes the original traceback

Python propagates unhandled exceptions upward until they are caught or terminate the program. This propagation model allows higher layers to centralize recovery or logging behavior.

Using 'raise' without arguments inside an except block re-raises the currently handled exception while preserving traceback details. The finally block still executes during exception flow, and exception chaining preserves rather than removes traceback context.

Question 16

Which of the following exceptions are commonly triggered during file-processing operations?

EASY
  • A FileNotFoundError
  • B PermissionError
  • C IndentationError
  • D UnicodeDecodeError

File-processing workflows frequently encounter missing files, permission restrictions, and character encoding problems. These exceptions are common in ETL pipelines, log-processing systems, and cross-platform integrations.

IndentationError is unrelated to runtime file operations because it occurs during Python code parsing before execution begins.

Question 17

Write Python code that retries an API call up to three times when a temporary connection error occurs.

MEDIUM

This example demonstrates controlled retry behavior for temporary operational failures. The code retries only a specific exception type rather than blindly retrying every failure condition.

In real systems, this pattern is commonly used for HTTP integrations, queue consumers, and cloud-service communication where transient outages are expected. Adding delays between retries helps reduce pressure on overloaded systems.

// Python
import time

class TemporaryAPIError(Exception):
    pass

attempt = 0
max_attempts = 3

while attempt < max_attempts:
    try:
        attempt += 1

        if attempt < 3:
            raise TemporaryAPIError('Temporary connection failure')

        print('API request successful')
        break

    except TemporaryAPIError as e:
        print(f'Attempt {attempt} failed: {e}')

        if attempt == max_attempts:
            print('Max retry limit reached')
            raise

        time.sleep(2)
Question 18

Create a Python context manager that logs exceptions before re-raising them.

HARD

The context manager intercepts exceptions through the __exit__ method. When an exception occurs, the manager logs details and returns False so the exception continues propagating upward.

This pattern is widely used in enterprise logging frameworks, transaction wrappers, and monitoring instrumentation where centralized error visibility is required without suppressing failures.

// Python
class ErrorLogger:
    def __enter__(self):
        print('Starting operation')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print(f'Logged exception: {exc_value}')
        return False

try:
    with ErrorLogger():
        result = 10 / 0
except ZeroDivisionError:
    print('Exception propagated to caller')
Question 19

Write Python code that validates user input and raises meaningful exceptions for invalid values.

MEDIUM

The function separates type validation from business-rule validation. This distinction helps upstream systems respond differently depending on the failure category.

Meaningful exceptions improve API usability because consumers can quickly understand whether the issue relates to malformed input, invalid business logic, or internal system behavior.

// Python
class InvalidAgeError(Exception):
    pass


def validate_age(age):
    if not isinstance(age, int):
        raise TypeError('Age must be an integer')

    if age < 0:
        raise InvalidAgeError('Age cannot be negative')

    return 'Valid age'

try:
    print(validate_age(-5))
except (TypeError, InvalidAgeError) as e:
    print(f'Validation failed: {e}')
Question 20

Demonstrate how to preserve the original traceback while adding additional context to an exception.

HARD

The code catches a low-level ValueError and raises a higher-level DataPipelineError with additional business context. The original traceback remains attached because of exception chaining.

This technique is especially valuable in ETL systems, batch-processing pipelines, and integration platforms where raw exceptions alone do not provide enough operational insight.

// Python
class DataPipelineError(Exception):
    pass


def process_record(record):
    try:
        value = int(record['amount'])
        return value
    except ValueError as e:
        raise DataPipelineError(
            f'Failed processing record ID {record.get("id")}'
        ) from e

sample = {
    'id': 101,
    'amount': 'invalid-number'
}

try:
    process_record(sample)
except DataPipelineError as e:
    print(e)
Question 21

Explain the difference between 'except Exception:' and 'except ValueError:' in Python.

EASY

'except Exception:' is a broad handler that catches most built-in exceptions, including runtime errors such as TypeError, IndexError, and ValueError. It is useful for generic error handling but can mask unexpected issues.

'except ValueError:' is specific to the ValueError exception. It only triggers when an operation raises this particular type, such as converting a non-numeric string to an integer.

Using specific exceptions improves code readability and maintainability by handling only expected error scenarios, while generic exceptions should be used sparingly for fallback logic or logging.

Question 22

How can Python's 'else' block in exception handling improve code clarity?

MEDIUM

The 'else' block executes only if no exceptions are raised in the try block. This clearly separates the normal execution path from error handling logic.

For example, database transactions or file operations can perform actual business logic in 'else' after safely validating inputs in 'try'. This prevents nesting logic inside try blocks and makes the code easier to read.

Using 'else' helps avoid accidental catching of unrelated exceptions and emphasizes that the code inside 'else' is executed under normal conditions only.

Question 23

Describe the benefits and pitfalls of using multiple except clauses in Python exception handling.

HARD

Multiple except clauses allow different exception types to be handled with tailored logic. This improves clarity and ensures the program responds appropriately to each failure type.

A benefit is the ability to separate recoverable errors from critical failures, providing specific remediation for each case.

Pitfalls include overly complex exception trees that can be hard to maintain and the risk of catching exceptions in the wrong order, since Python matches exceptions from top to bottom. Broad exceptions should come last to avoid masking specific handlers.

Question 24

Which of the following statements about Python's finally block are true?

EASY
  • A It executes only if no exception occurs
  • B It executes whether or not an exception occurs
  • C It can be used for cleanup operations
  • D It prevents exceptions from propagating

The finally block always executes, making it suitable for closing files, releasing locks, or cleaning resources.

It does not suppress exceptions; exceptions still propagate unless explicitly handled inside finally.

Question 25

Which scenarios justify raising a custom exception in Python?

MEDIUM
  • A To signal an application-specific business rule violation
  • B To replace all standard Python exceptions
  • C To attach additional metadata to an error
  • D To avoid using try-except blocks entirely

Custom exceptions provide clarity by representing domain-specific failures, allowing different handling strategies per error type.

They can also carry extra data, such as error codes or context, aiding in logging and recovery. Using them to replace standard exceptions or avoid try-except is not recommended.

Question 26

Which practices are recommended for exception handling in production Python systems?

HARD
  • A Logging exceptions with detailed traceback
  • B Catching only specific exceptions when possible
  • C Using bare except clauses for all errors
  • D Raising new exceptions with context using 'from'

Detailed logging preserves tracebacks and helps in debugging and incident analysis.

Catching specific exceptions avoids masking unexpected errors, improving reliability.

Exception chaining with 'from' preserves original context while providing higher-level error information. Bare except clauses are discouraged because they hide problems.

Question 27

Write Python code to safely convert a user input string to integer using exception handling.

EASY

The code attempts to convert a string to an integer inside a try block.

If the string is invalid, a ValueError is raised and caught, preventing the program from crashing and providing a meaningful message to the user.

// Python
user_input = 'abc'
try:
    number = int(user_input)
    print(f'Converted number: {number}')
except ValueError:
    print(f'Invalid input: {user_input}')
Question 28

Implement a Python function that reads a file and returns its content, raising a custom exception if the file is missing.

MEDIUM

The function uses a context manager to open the file safely and automatically close it after reading.

If the file is missing, it raises a custom exception to provide clear, domain-specific error information, which is helpful for API consumers or higher-level logic.

// Python
class FileMissingError(Exception):
    pass

def read_file(file_path):
    try:
        with open(file_path, 'r') as f:
            return f.read()
    except FileNotFoundError:
        raise FileMissingError(f'File not found: {file_path}')

try:
    content = read_file('nonexistent.txt')
except FileMissingError as e:
    print(e)
Question 29

Write Python code demonstrating re-raising an exception after logging it.

HARD

The code catches a ZeroDivisionError, logs its details, and then re-raises it so that it can propagate to higher layers.

Re-raising preserves the original traceback while allowing intermediate logging, which is essential for debugging and monitoring in production systems.

// Python
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f'Logging error: {e}')
    raise
Question 30

Create a Python function that validates a dictionary key exists and raises a custom exception with context if not.

MEDIUM

The function attempts to access a key in a dictionary. If the key is missing, a custom MissingKeyError is raised.

Using 'from e' chains the original KeyError, preserving the traceback and providing both context and clarity for debugging and error handling.

// Python
class MissingKeyError(Exception):
    pass

def validate_key(data, key):
    try:
        return data[key]
    except KeyError as e:
        raise MissingKeyError(f'Key {key} missing in data') from e

sample = {'name': 'Alice'}
try:
    validate_key(sample, 'age')
except MissingKeyError as e:
    print(e)