Return codes are easy to ignore or lose across several functions. An uncaught C++ exception has the opposite problem: it ends the program. Between the two sits the mechanism worth learning: throw moves a failure off the return path, the first compatible catch block claims it, and every local object in the abandoned scopes is destroyed on the way.
Exception handling in C++: what try, throw and catch mean
An exception is an object that reports a failure outside a function's normal return path. The flow has four steps: execution enters a try block, throw creates or propagates an exception, the remaining statements in that try block are skipped, and the first compatible handler runs.
try {
operation();
} catch (const std::exception& error) {
std::cout << error.what();
}The catch block is the handler, and error names the exception object. As control transfers to that handler, stack unwinding destroys the automatic local objects in every scope it abandons.
C has no such mechanism, so the same failures travel as return codes and errno, the style set out in C Programming for Teaching CS Exams: Key Concepts. Exceptions do not replace every error code either: a search that legitimately finds nothing is a return value, not a failure. Related language tracks sit under Coding & Skill Development.
C++ exception handling example: divide 84 safely
The program protects integer division with a specific exception type:
#include <iostream>
#include <stdexcept>
int safeDivide(int numerator, int denominator) {
if (denominator == 0) {
throw std::invalid_argument("divisor must not be zero");
}
return numerator / denominator;
}
int main() {
int denominators[] = {7, 0};
for (int denominator : denominators) {
try {
int result = safeDivide(84, denominator);
std::cout << "84 / " << denominator << " = "
<< result << '\n';
} catch (const std::invalid_argument& error) {
std::cout << "Cannot divide 84 by " << denominator
<< ": " << error.what() << '\n';
}
}
}First, denominator is 7. The guard is false, so integer division computes 84 / 7 = 12, returns 12, and the try block prints it.
Next, denominator is 0. The guard is true, so safeDivide throws before division. Control skips the remaining try statements, enters the matching handler, and gets the stored message from error.what().
84 / 7 = 12
Cannot divide 84 by 0: divisor must not be zeroCatching by const reference avoids a copy and preserves the dynamic exception type. Equality with zero is appropriate here because the denominator is an integer.

C++ catch matching: standard exception types and handler order
Choose the narrowest standard type that accurately describes the failure.
Exception type | Suitable use |
|---|---|
| A value violates a function contract |
| An index or bound is invalid |
| A runtime failure has no more specific standard type |
| A common-base fallback for standard exceptions |
For throw std::out_of_range("index 4, size 3");, put catch (const std::out_of_range& error) before catch (const std::exception& error). Handlers are tested top to bottom, so the narrower handler claims the exception and error.what() yields index 4, size 3. Reverse the two and the std::exception handler runs instead, because it matches as well and now comes first. A final catch (...) can handle an unknown type but cannot inspect what().
After a handled exception, execution continues after the handler. If the current function has no compatible handler, propagation continues up the call stack. If no handler exists anywhere, C++ calls std::terminate.
Stack unwinding in C++: a worked 800-versus-1200 trace
Stack unwinding makes destruction during propagation observable:
#include <iostream>
#include <stdexcept>
#include <string>
struct Trace {
std::string name;
explicit Trace(const std::string& value) : name(value) {}
~Trace() {
std::cout << "destroy " << name << '\n';
}
};
void debit(int available, int requested) {
Trace debitTrace{"debit-local"};
if (requested > available) {
throw std::runtime_error(
"requested " + std::to_string(requested) +
", available " + std::to_string(available));
}
}
void checkout() {
Trace checkoutTrace{"checkout-local"};
debit(800, 1200);
}
int main() {
try {
checkout();
} catch (const std::runtime_error& error) {
std::cout << error.what() << '\n';
}
}checkout() creates its trace, then debit(800, 1200) creates the inner trace. Because 1200 > 800, debit throws. C++ destroys the automatic object in the innermost active scope, then the caller's object, before the handler in main runs:
destroy debit-local
destroy checkout-local
requested 1200, available 800This is RAII in action. A destructor can release a file, lock or memory during unwinding. It must not let another exception escape while the first is unwinding.

Custom C++ exceptions, rethrowing and noexcept
A domain-specific failure can carry both a useful message and structured data:
class InsufficientFunds : public std::runtime_error {
public:
int requested;
int available;
InsufficientFunds(int req, int avail)
: std::runtime_error("requested " + std::to_string(req) +
", available " + std::to_string(avail)),
requested(req), available(avail) {}
};Construct InsufficientFunds(1200, 800), throw it by value, and catch it as const InsufficientFunds&: what() returns requested 1200, available 800, and the handler can still read error.requested and error.available as plain integers. This use of a derived class builds on the inheritance model explained in OOP for Teaching CS Exams: Classes and Inheritance.
Once debit throws InsufficientFunds in place of the plain std::runtime_error, an intermediate checkout() handler can log and rethrow:
try {
debit(800, 1200);
} catch (const InsufficientFunds&) {
std::cout << "checkout failed for request 1200\n";
throw;
}Bare throw; preserves the current exception. throw error; starts a new throw expression and can slice an object held as a base type.
noexcept promises that no exception will leave a function. If one does, C++ calls std::terminate. Use it only when the contract supports that promise. Exceptions suit failures, not routine branches such as a menu choice or search miss.
C++ exception handling mistakes and their fixes
Catching by value: this can copy or slice a polymorphic exception. Catch by
constreference.General handler first:
std::exceptioncaptures the specific case. Order handlers from specific to general.Throwing a pointer:
throw new std::runtime_error(...)creates ownership risks. Throw an object by value.Empty catch-all: empty
catch (...)hides failure. Handle, translate or rethrow it.Throwing from cleanup: a second exception escaping a destructor can terminate the program. Keep cleanup non-throwing.
Exceptions for expected input: entering
Nat a yes-or-no prompt is normal. Return a value, and reserve exceptions for failures the caller cannot handle locally.
How interviews and MCQs test exception handling in C++
Work through these small traces and predict their results.
safeDivide(45, 5)follows the normal path. Answer:45 / 5 = 9, so the function returns9.safeDivide(45, 0)triggers the zero guard. Answer: it enters thestd::invalid_argumenthandler without attempting division.std::out_of_range("index 4, size 3")is thrown withstd::exceptionlisted first. Answer: the base-handler label prints, exposing the wrong order.
For a nested trace, let the inner handler print inner and execute bare throw;. Let the outer handler print outer. The output is:
inner
outerThe rethrow preserves the exception type. Be ready to explain handler matching, propagation, reverse destruction during unwinding, and the consequence of violating noexcept.
Exception handling in C++: the short version and next step
Detect a failure, throw a meaningful object, catch the most specific useful type by const reference, and let RAII clean active scopes. Uncaught exceptions and exceptions escaping noexcept end in termination.
The rest of the language in dependency order is laid out in the C++ Tutorial: Complete Learning Path, and the sequenced C, C++, Java, Python and competitive-coding track is Coding For Placements. If you need only this concept, compile both examples, change 84, 7, 800 and 1200, predict every output line, and then run the programs.




