Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to report an error

Exceptions, std::optional, std::expected, std::variant, and the error codes they replace. Which one fits which kind of failure.

Ask how ordinary the failure is, and whether the caller needs to know why.

  • Rare, and the code that notices it can't handle it (often several calls below the code that can), or a constructor that must refuse → throw an exception.
  • "No result" is a normal answer, and the reason is obvious (a search that finds nothing) → std::optional.
  • Failure is an ordinary outcome and the caller needs the reason, or it happens often in a loop, or the code is built without exceptions → std::expected.
  • Several kinds of legitimate result, none of them a failure → std::variant. That's not error handling at all, but it's the tool people reach for by mistake.

Don't throw for ordinary control flow, and don't return an optional when the caller will immediately ask "but why?" Mixing tools in one program is normal.

Exceptions

The error travels up the call stack to the nearest matching catch.

Why RAII makes exceptions safe, and the promise that a function never throws.

Results in the return type

Choosing, and the neighbor that isn't an error

The four-row table this cluster's decision list comes from.

The Old Way, and the C boundary

A return of 0 or -1 and a result written through a pointer, and what replaces them.

Finding out it went wrong at all

An error path no test reaches is an error path nobody has checked.