Exception Handling in C++


Exception Handling in C++ Interview with follow-up questions

1. What is exception handling in C++ and why is it important?

Exception handling in C++ is a structured mechanism for detecting, signalling, and recovering from error conditions that cannot (or should not) be handled locally. It separates the code that detects an error from the code that handles it, without requiring every intermediate function to check and forward error codes.

The three keywords involved are try, throw, and catch.

Why it matters:

  1. Separation of concerns: Normal logic and error-recovery logic are in distinct blocks, making code easier to read and maintain.

  2. Propagation without boilerplate: An exception thrown deep in a call stack automatically propagates upward through every intermediate function until a matching catch is found — no manual error-code forwarding required.

  3. RAII integration: C++ exception handling works hand-in-hand with destructors. When a stack unwinds due to an exception, all local objects with destructors are destroyed in reverse construction order. This guarantees that std::unique_ptr, std::lock_guard, file handles, and other RAII wrappers release their resources even on the exception path — this is one of the most important safety properties of C++.

  4. Type-safe error signalling: Exceptions carry typed objects, allowing different catch handlers to respond to different error categories.

When not to use exceptions:

  • Performance-critical hot paths: Exception handling has near-zero overhead when no exception is thrown (zero-cost exceptions with modern ABIs), but throwing and catching is expensive. Do not use exceptions for expected control flow.
  • Embedded/real-time systems: Many such environments disable exceptions entirely (-fno-exceptions); use error codes or std::expected (C++23) instead.
  • noexcept functions: Mark functions that genuinely cannot throw as noexcept — this enables compiler optimizations and communicates intent clearly. Destructors are implicitly noexcept.

Modern practice (C++23): std::expected provides an alternative for functions that want to return either a value or an error without using exceptions, similar to Rust's Result.

↑ Back to top

Follow-up 1

Can you explain the difference between error and exception?

In C++, an error is a problem that occurs during the execution of a program and prevents it from continuing. Errors can be categorized into compile-time errors and runtime errors. Compile-time errors are detected by the compiler and prevent the program from being compiled. Runtime errors occur during the execution of the program and can cause it to terminate abruptly.

On the other hand, an exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. Exceptions are typically caused by exceptional conditions or errors that can be handled by the program. Unlike errors, exceptions can be caught and handled by the program, allowing it to continue its execution.

Follow-up 2

What are the key components of exception handling in C++?

The key components of exception handling in C++ are:

  1. Try block: It is used to enclose the code that may throw an exception. If an exception is thrown within the try block, it is caught and handled by the corresponding catch block.

  2. Throw statement: It is used to explicitly throw an exception. It can be used to throw built-in types, user-defined types, or even objects of standard library classes.

  3. Catch block: It is used to catch and handle exceptions thrown by the try block. Multiple catch blocks can be used to handle different types of exceptions.

  4. Exception object: When an exception is thrown, an object containing information about the exception is created. This object can be accessed and used within the catch block to handle the exception.

  5. Standard exception classes: C++ provides a set of standard exception classes that can be used to handle common types of exceptions. These classes are defined in the header.

Follow-up 3

How does exception handling improve the robustness of a program?

Exception handling improves the robustness of a program in several ways:

  1. Prevents program crashes: Exception handling allows the program to gracefully handle errors and exceptional situations. Instead of crashing, the program can catch and handle the exception, preventing the program from terminating abruptly.

  2. Separates error-handling code: Exception handling separates the error-handling code from the normal code flow. This makes the code more readable and maintainable. It also reduces the chances of bugs and makes it easier to debug and fix issues.

  3. Provides a centralized error-handling mechanism: Exception handling provides a centralized mechanism to handle errors. Instead of scattering error-handling code throughout the program, exceptions can be caught and handled in a single location, making it easier to manage and maintain.

  4. Supports graceful recovery: Exception handling allows the program to recover from errors and continue its execution. By catching and handling exceptions, the program can take appropriate actions to recover from the error and continue its normal flow of execution.

2. Can you explain the try-catch block in C++ with an example?

In C++, the try-catch block separates code that may throw an exception from the code that handles it. Code inside the try block executes normally; if any statement throws, execution jumps immediately to the matching catch block and the rest of the try block is skipped.

Basic example:

#include 
#include 

double divide(double a, double b) {
    if (b == 0.0)
        throw std::invalid_argument("division by zero");
    return a / b;
}

int main() {
    try {
        double result = divide(10.0, 0.0);
        std::cout << "Result: " << result << '\n';  // skipped
    }
    catch (const std::invalid_argument& e) {
        std::cerr << "Error: " << e.what() << '\n';
    }
}

Multiple catch blocks — ordered from most specific to most general:

try {
    riskyOperation();
}
catch (const std::out_of_range& e) {
    std::cerr << "Range error: " << e.what() << '\n';
}
catch (const std::runtime_error& e) {
    std::cerr << "Runtime error: " << e.what() << '\n';
}
catch (const std::exception& e) {
    std::cerr << "Unexpected error: " << e.what() << '\n';
}
catch (...) {
    // catch-all for non-std exceptions (e.g., thrown integers)
    std::cerr << "Unknown exception\n";
}

Important rules:

  • Always catch exceptions by const reference (const std::exception&), not by value. Catching by value slices derived exception objects and copies unnecessarily.
  • The catch(...) handler catches anything, including non-class types. It is typically used as a last resort or in destructors to prevent exceptions from escaping.
  • Stack unwinding: when an exception propagates, all local variables between the throw site and the matching catch are destroyed in reverse order — RAII handles resource cleanup automatically.
  • A try block can be nested; the inner catch has the first opportunity to handle the exception, and can rethrow with a bare throw; to pass it to an outer handler.

noexcept and termination: if an exception propagates out of a noexcept function, std::terminate is called immediately — there is no further stack unwinding. This is why destructors are noexcept by default.

↑ Back to top

Follow-up 1

Can there be multiple catch blocks for a single try block?

Yes, there can be multiple catch blocks for a single try block. Each catch block can handle a different type of exception. The catch blocks are evaluated in the order they appear, and the first catch block that matches the type of the thrown exception will be executed. If no catch block matches the type of the thrown exception, the exception will propagate to the next level of the call stack.

Follow-up 2

What happens if an exception is not caught?

If an exception is not caught, it will result in program termination. When an exception is thrown and not caught by any catch block, the program will terminate and an error message will be displayed. This is known as an unhandled exception.

Follow-up 3

How does the catch block determine which exception to catch?

The catch block determines which exception to catch based on the type of the thrown exception. When an exception is thrown, the catch blocks are evaluated in the order they appear. The first catch block that matches the type of the thrown exception will be executed. If no catch block matches the type of the thrown exception, the exception will propagate to the next level of the call stack.

For example:

try {
    // Code that may throw an exception
    throw MyException();
}
catch (const std::exception& e) {
    // Catch block for std::exception and its derived classes
    cout << "Caught std::exception: " << e.what() << endl;
}
catch (const MyException& e) {
    // Catch block for MyException
    cout << "Caught MyException" << endl;
}

In this example, if a MyException object is thrown, it will be caught by the second catch block. If a std::exception object or any of its derived classes are thrown, they will be caught by the first catch block.

3. What is the use of the throw keyword in C++?

The throw keyword is used to signal that an exceptional condition has occurred. It immediately transfers control out of the current function, unwinding the call stack until a matching catch block is found.

Throwing a standard exception:

#include 

void setAge(int age) {
    if (age < 0 || age > 150)
        throw std::out_of_range("Age must be between 0 and 150");
}

Throwing any type — you can throw any copyable type, but throwing objects derived from std::exception is strongly preferred because it integrates with standard catch handlers:

throw 42;              // legal but poor practice
throw "error";         // legal but poor practice
throw std::runtime_error("something went wrong");  // idiomatic

Rethrowing — inside a catch block, a bare throw; with no operand rethrows the current exception object without slicing it:

catch (const std::exception& e) {
    log(e.what());
    throw;  // propagate the original exception unchanged
}

throw in expressions vs. throw as a statement:

Since C++11, throw can appear as an expression (yielding type void), which enables patterns like:

int value = (ptr != nullptr) ? *ptr : throw std::runtime_error("null ptr");

What happens after throw:

  1. The thrown object is copy- or move-constructed into a special storage area (exception object).
  2. The runtime begins stack unwinding — destructors of all local objects between the throw site and the matching catch are called in reverse construction order.
  3. The first catch block whose parameter type matches the exception type (or a base class of it) takes control.
  4. If no matching catch is found anywhere on the call stack, std::terminate is called.

noexcept interaction: throwing from a function declared noexcept calls std::terminate directly — stack unwinding does not occur. This is intentional: noexcept functions make a hard contract that no exception will escape.

↑ Back to top

Follow-up 1

Can you throw exceptions of any data type?

Yes, in C++, you can throw exceptions of any data type. The thrown exception can be of a built-in data type, a user-defined data type, or even a pointer or reference type.

Follow-up 2

What happens after an exception is thrown?

After an exception is thrown, the program flow is interrupted and the control is transferred to the nearest catch block that can handle the exception. If there is no catch block that can handle the exception, the program terminates and an error message is displayed.

Follow-up 3

Can you provide an example of using throw in a program?

Sure! Here's an example of using throw in a program:

#include 

int divide(int a, int b) {
    if (b == 0) {
        throw "Division by zero exception";
    }
    return a / b;
}

int main() {
    try {
        int result = divide(10, 0);
        std::cout << "Result: " << result << std::endl;
    }
    catch (const char* exception) {
        std::cout << "Exception caught: " << exception << std::endl;
    }
    return 0;
}

In this example, the divide function throws an exception with the message "Division by zero exception" if the second argument is zero. The exception is then caught in the main function using a catch block that handles exceptions of type const char*.

4. What is the purpose of the standard exception library in C++?

The standard exception library, defined in and, provides a hierarchy of pre-built exception classes that cover the most common runtime error categories. Using these classes (rather than throwing raw integers or strings) gives every caller a uniform interface for inspecting exceptions.

The hierarchy (rooted in ``):

std::exception
├── std::logic_error        — errors detectable before runtime (bad arguments, precondition violations)
│   ├── std::invalid_argument
│   ├── std::domain_error
│   ├── std::length_error
│   └── std::out_of_range
└── std::runtime_error      — errors only detectable at runtime
    ├── std::range_error
    ├── std::overflow_error
    ├── std::underflow_error
    └── std::system_error   (C++11 — wraps OS/library error codes)
        └── std::ios_base::failure

There are also library-specific exceptions not in this tree: std::bad_alloc (thrown by new), std::bad_cast (thrown by dynamic_cast), std::bad_optional_access (C++17), and others.

Why use standard exceptions:

  1. Common interface: all derive from std::exception, so a single catch (const std::exception& e) handler can catch everything and call e.what() to get a description.
  2. Semantic clarity: std::out_of_range immediately communicates a bounds problem; std::invalid_argument communicates a bad input — no comment needed.
  3. Interoperability: third-party libraries and the standard library itself throw from this hierarchy, so your catch handlers work uniformly.

Extending the hierarchy — the idiomatic way to define application-specific exceptions:

class DatabaseError : public std::runtime_error {
public:
    explicit DatabaseError(const std::string& msg)
        : std::runtime_error(msg) {}
};

// Caught by either:
catch (const DatabaseError& e) { /* specific */ }
catch (const std::runtime_error& e) { /* general */ }
catch (const std::exception& e) { /* catch-all std */ }

C++23 note: std::expected complements the exception library for situations where error handling should be explicit and non-throwing, similar to Rust's Result type.

↑ Back to top

Follow-up 1

Can you name a few commonly used classes in the standard exception library?

Yes, here are a few commonly used classes in the standard exception library:

  • std::exception: The base class for all standard exceptions. It provides a virtual member function what() that returns a C-style string describing the exception.
  • std::runtime_error: Represents errors that can occur during runtime, such as logical errors or resource exhaustion.
  • std::logic_error: Represents errors that are caused by incorrect programming logic, such as invalid arguments or incorrect use of functions.
  • std::out_of_range: Represents errors that occur when accessing elements out of the valid range, such as array index out of bounds or iterator out of range.
  • std::bad_alloc: Represents errors that occur when memory allocation fails.

Follow-up 2

How can we create our own exception classes?

To create our own exception classes in C++, we can derive them from the base class std::exception or any of its derived classes. Here is an example of creating a custom exception class:

#include 

class MyException : public std::exception {
public:
    const char* what() const noexcept override {
        return "My custom exception";
    }
};

In this example, MyException is derived from std::exception and overrides the what() function to provide a custom error message. We can then throw and catch instances of MyException in our program.

Follow-up 3

What is the hierarchy of standard exception classes in C++?

In C++, the standard exception classes form a hierarchy with std::exception as the base class. Here is a simplified hierarchy of some commonly used standard exception classes:

std::exception
├── std::runtime_error
│   ├── std::logic_error
│   │   ├── std::invalid_argument
│   │   ├── std::domain_error
│   │   ├── std::length_error
│   │   └── ...
│   └── std::out_of_range
└── std::bad_alloc

This hierarchy allows for catching exceptions at different levels of specificity. For example, catching std::exception will catch all standard exceptions, while catching std::runtime_error will catch exceptions related to runtime errors.

5. What is the concept of exception propagation in C++?

Exception propagation is the automatic process by which an unhandled exception travels up the call stack from the point where it was thrown until a matching catch block is found.

How it works:

  1. A throw expression is evaluated — the exception object is constructed and stored in special storage managed by the runtime.
  2. The current function exits immediately (without executing any remaining statements).
  3. The runtime unwinds the stack: for every stack frame exited, destructors of all local objects in that frame are called in reverse construction order (this is RAII at work).
  4. At each frame, the runtime checks whether that function's call site is inside a try block with a matching catch.
  5. If a match is found, control transfers to that catch block.
  6. If no matching catch is found anywhere on the stack, std::terminate is called and the program ends.

Example showing propagation through multiple frames:

void c() {
    throw std::runtime_error("error in c");
}

void b() {
    c();  // exception propagates through here
}

void a() {
    try {
        b();  // exception propagates through here
    }
    catch (const std::runtime_error& e) {
        std::cerr << "Caught in a: " << e.what() << '\n';
    }
}

c() throws, exits, b() exits (its locals are destroyed), and a()'s try block catches the exception. Neither b() nor c() needed any error-handling code.

Rethrowing during propagation:

A catch block can rethrow the current exception with a bare throw; — this propagates the original exception object (including its dynamic type) without slicing:

catch (const std::exception& e) {
    log(e.what());
    throw;  // continue propagating
}

Writing throw e; instead would slice the exception to std::exception — always prefer bare throw; to rethrow.

noexcept and propagation: if an exception propagates into a function declared noexcept, std::terminate is called immediately and the rest of the stack is not unwound. Destructors are implicitly noexcept for this reason — an exception escaping a destructor during stack unwinding would call std::terminate.

std::current_exception / std::rethrow_exception (C++11): these allow capturing an in-flight exception as a std::exception_ptr and rethrowing it later, enabling exception transport across threads (e.g., std::future uses this internally).

↑ Back to top

Follow-up 1

Can you explain with an example how exception propagation works?

Sure! Here's an example:

#include 

void functionB()
{
    throw std::runtime_error("Exception in function B");
}

void functionA()
{
    functionB();
}

int main()
{
    try
    {
        functionA();
    }
    catch(const std::exception& e)
    {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

In this example, when functionB() throws an exception, it is propagated to functionA(), and then to main(). Finally, the exception is caught in the catch block in main() and the error message is printed.

Follow-up 2

What happens if an exception is not caught in the current function?

If an exception is not caught in the current function, it will be propagated to the calling function. This process continues until the exception is caught and handled or until it reaches the top-level function. If the exception reaches the top-level function without being caught, the program terminates and an error message is displayed.

Follow-up 3

Can an exception be caught in another function other than the one it was thrown in?

Yes, an exception can be caught in another function other than the one it was thrown in. When an exception is thrown, the program searches for a matching catch block in the current function. If a matching catch block is not found, the exception is propagated to the calling function. This process continues until a matching catch block is found or until the exception reaches the top-level function. So, it is possible to catch an exception in a function that is higher up in the call stack than the function where the exception was thrown.

Live mock interview

Mock interview: Exception Handling in C++

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.