1. What Is It?

When something goes wrong in a function — file not found, bad input, network failure — the function has to tell its caller. There are two fundamentally different ways: throw an (a separate, out-of-band channel that unwinds the until someone catches it) or return an error value (the normal return path, but the value indicates "this didn't work").

Without a clear picture of which model a language uses and when to reach for which, you get code that either silently swallows failures or wraps every call in a paranoid .

Quick check

A backend API function attempts to read a configuration file at startup. If the file is missing, the function uses the exception-based error model. Which behavior best describes how this error is communicated to the caller?

Choose one answer

2. How It Works

Exceptions

An is a special "thrown" from where the error occurred. Normal execution halts; the runtime walks up the call , discarding frames, until it finds a matching catch/except handler. If none exists, the program crashes with a trace.

# Python
def read_age(s):
    return int(s)          # raises ValueError if not numeric

try:
    age = read_age("abc")
except ValueError as e:
    print(f"bad input: {e}")

Error values

An error value is a regular return that encodes success or failure in its type. The caller must inspect it. Go uses the (result, err) tuple idiom; Rust uses Result<T, E>; C uses sentinel integers (-1, NULL). In Python and Java, you can simulate it with Optional, tuples, or sum types — but the native failure channel is exceptions.

# Python — error-value style (uncommon but valid)
def read_age(s):
    if not s.isdigit(): return None
    return int(s)

age = read_age("abc")
if age is None:
    print("bad input")

Stack unwinding vs. explicit propagation

With exceptions, callers who don't care about the error don't need to do anything — the sails past them. With error values, every caller must explicitly pass the error up (or handle it). That's more ceremony but more explicit.

An exception leaves the normal return path

The doc’s Python example raises ValueError for abc, unwinds read_age, and reaches the caller’s matching handler.

The caller asks read age to convert the text a b c inside a try block.

Text version
  1. The caller asks read age to convert the text a b c inside a try block.
  2. Integer conversion raises ValueError because a b c is not a number.
  3. The runtime removes the read age frame, so the caller never assigns an age.
  4. The caller catches ValueError because its handler matches the exception.
  5. The handler prints the error, and the program continues after the try block.
Quick check

A backend API has a deep call chain: handleRequest() → parseBody() → validateUser() → fetchRecord(). The fetchRecord() function encounters a database error. If the team uses exceptions instead of error values, what happens to the intermediate functions validateUser() and parseBody()?

Choose one answer

3. What You Actually Need to Know

  • Exceptions are for exceptional conditions, not normal control flow. Using them to signal "user chose option 2" is a classic anti-pattern — it's slow, obscures intent, and breaks traces as diagnostic tools.
  • Error values force the caller to confront failure; exceptions don't. Exceptions are great until you forget to catch one and it crashes production. Result<T, E> / Optional<T> are great until 80% of your code is error-propagation boilerplate.
  • Performance: throwing and catching an is expensive ( unwinding, allocation). In a hot loop, error values win. For one-off operations, the cost is negligible and the code clarity matters more.
  • Java has checked exceptions (throws SomeException in the signature). The compiler enforces that callers either catch or declare them. This is controversial — some love the explicitness, others find it leads to throws everywhere or catch-and-ignore blocks.
  • Python, C++, and Java runtime exceptions are unchecked — the compiler doesn't force you to handle them. You learn about them from docs, conventions, or production crashes.
  • C has no exceptions. Errors are always values (errno, return codes, output pointers). Same for Go, Rust, older C++ codebases, and many embedded / systems contexts.
  • Debugging clues:
    • Uncaught exception with a clean stack trace → great — the trace tells you exactly where and why.
    • Missing error check → function returns a result you didn't look at; silently wrong output downstream.
    • catch (Exception e) {} → swallowed error; a future developer (or you at 3am) won't know the failure happened.
    • try block around 200 lines → too broad; narrow to the single operation that might fail.
Quick check

A backend service processes payments in a high-throughput loop — thousands of transactions per second. The current implementation throws a custom exception whenever a transaction is declined (a common, expected outcome). A teammate suggests replacing this with an error value (e.g., a Result type or return code). What is the strongest technical reason to make this change?

Choose one answer

4. Language Differences

AspectPythonJavaC++
Native failure channelExceptionsExceptions (checked + unchecked)Exceptions
Checked exceptions?NoYes (unless extending RuntimeException)No
Error-value idiomsOptional, tuples, sentinel NoneOptional<T>, explicit wrapper classesstd::optional, std::expected (C++23), error codes
Cost of throwingModerate (Python exceptions are relatively cheap)Moderate (stack capture is the expensive part)Can be zero-cost on success path but very expensive on throw
Standard practiceExceptionsExceptions (but Optional common for "not found")Depends on codebase / domain — games and embedded often disable them entirely

Go and Rust (both popular modern systems languages) chose error values as the default. Their influence is pushing C++ and Python codebases toward more Result/expected patterns where latency or clarity matter.

Java adds a checked exception contract

Python, Java, and C++ use exceptions, but Java also enforces handling or declaration for checked exceptions.

Python, Java, and C plus plus all use exceptions as a native failure channel.

Text version
  1. Python, Java, and C plus plus all use exceptions as a native failure channel.
  2. Java enforces catching or declaring checked exceptions, while runtime exceptions are unchecked.
  3. All three can return failure as a value, so callers can inspect the result.
Quick check

You are writing a high-performance C++ game engine where certain operations must complete with minimal latency. A colleague suggests using C++ exceptions for all error handling. What is the strongest performance-related argument against this approach in this context?

Choose one answer

5. Tradeoffs & Decisions

  • Exceptions:

    • Pros: normal-path code stays clean; failures can be handled far from where they happen; traces are powerful debugging aids.
    • Cons: easy to forget to handle; hide control flow; slow when thrown; can leak resources if RAII / try-finally isn't used correctly.
  • Error values:

    • Pros: failure is explicit in the signature; performance is predictable; no hidden control flow; forces the caller to decide.
    • Cons: verbose — every call site needs a check or a propagation; pipelines of calls require ? (Rust), monadic chains (.flatMap), or nested if blocks.
  • Choose by category of failure:

    • Programmer error / bug (null pointer, index out of bounds): let it throw. You want the crash and the trace.
    • Expected failure the caller must decide about (file not found, invalid input, network timeout): error value or a specific caught close to the call.
    • Unrecoverable (out of memory, corrupted state): crash — don't try to handle it.

If you see a function with throws or catch (Exception), it usually hides a lack of thinking about what can actually go wrong. You'd choose error values when failure is routine enough to be part of the contract, exceptions when it's truly out-of-band.

Choose how callers receive failure

Exceptions keep the normal path clean but hide control flow. Error values make failure explicit, so each caller checks it or passes it up.

Exceptions keep normal code clean, while handling error values adds checks or propagation at call sites.

Text version
  1. Exceptions keep normal code clean, while handling error values adds checks or propagation at call sites.
  2. Exceptions skip callers with no handler, but an error value moves up only when each caller returns it.
  3. Throwing is slow because the runtime unwinds the stack, so error values win in a hot loop.
  4. For expected failure, return an error value or catch a specific exception close to the call.
Quick check

A backend service reads a configuration file at startup. If the file is missing, the service cannot proceed. A teammate suggests catching this with a broad catch (Exception e) and logging a generic error. What is the better design choice and why?

Choose one answer

6. Interview Cheat Sheet

  • Exceptions use a separate unwinding channel — throw here, catch anywhere up the . Error values use the normal return channel — callers inspect the result.
  • Exceptions keep the happy path clean but hide control flow; error values are explicit but verbose.
  • Java has checked exceptions — the compiler enforces handling or declaration. Python and C++ exceptions are unchecked.
  • Use exceptions for exceptional conditions, not control flow; they're expensive to throw and obscure intent when misused.
  • Modern languages (Rust, Go) prefer error values; modern C++ offers std::expected; modern Python often uses Optional / tuples for expected failures.
  • Bugs crash; expected failures are handled. If it's a programmer error, let it throw — the trace is your friend.

Follow-ups:

  • "When should you throw vs. return an error?" — Throw for exceptional, unexpected conditions. Return an error value when failure is a normal part of the function's contract.
  • "What's the problem with catch ( e)?" — It swallows every error, including bugs you wanted to hear about. Catch only what you can actually handle.
  • "Why do Go and Rust use error values instead of exceptions?" — Explicit failure in the type system, predictable performance, no hidden control flow — at the cost of verbosity.

Reading

On this page

My notes

Sign in to keep highlights and notes.

Glossary history

Click dotted jargon to save explanations here.