Error handling is the set of mechanisms a programming language provides for detecting, signalling, and responding to conditions that prevent normal program execution — including exceptions and try/catch/finally blocks, explicit error return values, Result and Option types, and checked vs. unchecked error models.
When things go wrong — how programs handle it
What you'll learn: What exceptions are and why they exist. try/except/finally as a universal pattern. The golden rule: fail loudly, recover intentionally.
How to read this tab: Compare with Python's Error Handling page which covers Python-specific syntax. This page explains the concept across all languages.
Error handling is the plan your program has for when something unexpected happens — instead of crashing silently, it catches the problem and decides what to do.
An exception is a signal that something went wrong — a structured way to stop normal execution and report the problem. The alternative is returning error codes (C's approach) — but error codes are easy to ignore silently. Exceptions cannot be ignored: if uncaught, they crash the program with a clear message. "Loud failure" is safer than "silent wrong result."
What is an exception?
An exception is a signal that something went wrong. Opening a missing file, dividing by zero, accessing an index out of bounds — these raise exceptions. Without handling, the program crashes. With handling, you intercept the exception and decide: recover, log, or re-raise.
The shape of exception handling is universal. try (risky code), catch/except (handle the error), finally (always runs). This appears in Python, Java, JavaScript, Go (defer), and Rust (Drop trait). The concept is identical; only the keywords differ. finally is particularly important: it guarantees cleanup even when an exception occurs.
Try / except / finally
try:
with open("data.csv") as f:
content = f.read()
except FileNotFoundError:
print("File not found — using default")
content = ""
except PermissionError as e:
print(f"Permission denied: {e}")
raise # re-raise: let it propagate
finally:
print("Always runs — cleanup goes here")Go and Rust treat errors as return values, not special control flow. Go functions return (result, error). Rust functions return Result
Result types: error as a value
Rust uses Result<T, E> — either Ok(value) or Err(error). The compiler requires you to handle both cases. Go conventionally returns (value, error) from fallible functions.
use std::fs;
fn read_file(path: &str) -> Result<String, std::io::Error> {
fs::read_to_string(path)
}
match read_file("data.csv") {
Ok(content) => println!("{}", content),
Err(e) => println!("Error: {}", e),
}
// ? operator: propagate error up the call stack
fn process() -> Result<(), std::io::Error> {
let content = read_file("data.csv")?;
println!("{}", content);
Ok(())
}Two rules for professional error handling: 1) Raise exceptions as soon as you detect bad input — don't let wrong data propagate deeper into the program. 2) Catch exceptions at the level that can actually do something about them — not everywhere just to silence them. The worst pattern: except: pass. It hides every error, including the bugs you need to find.
The golden rule: fail loudly, recover intentionally
Never silently swallow errors (except: pass is almost always wrong). Catch only what you can actually handle. Let unexpected errors propagate. A crash with a clear error message is better than a program continuing with corrupt state.
✅ Beginner tab complete — check your understanding
- I can explain what an exception is and why "crashing with a clear error" is better than silent failure
- I know the golden rule: raise exceptions early (when bad input arrives) and catch them late (at the level that can actually handle them)
- I know the difference between catching a specific exception vs catching all exceptions
Checked vs unchecked, Result types, and Option types
What you'll learn: Checked exceptions (Java) vs unchecked exceptions (Python, JavaScript). Result types — treating errors as values (Rust, Go). Option/Maybe types for nullable values.
How to read this tab: Understanding these different error models makes you appreciate the trade-offs each language makes. Go's error-as-value and Rust's Result
Java distinguishes checked (must be handled) from unchecked (can ignore). Python only has unchecked exceptions — you can always forget to catch them. Java's checked exceptions force you to handle or re-declare every checked exception, making error paths explicit in method signatures. Python's approach is more flexible; Java's is more explicit about error contracts.
Checked vs. unchecked exceptions
Java distinguishes: checked exceptions (must be declared with throws or caught — compile error otherwise; e.g. IOException) and unchecked exceptions (RuntimeException subclasses — no declaration needed; e.g. NullPointerException). Checked exceptions proved controversial in practice — they propagate through call chains, forcing many functions to declare exceptions they can't handle. Most modern languages (Python, Rust, Go, Kotlin, C#) avoid checked exceptions.
Python uses None where other languages use null. The difference: TypeScript's string | null forces you to check for null before using it (null safety). Python's Optional[str] hint says "this can be None" but doesn't enforce checking at runtime. Kotlin, Swift, and Rust all have language-level null safety; Python relies on discipline and type checkers.
Option types and null safety
Rust's Option<T> is either Some(value) or None. Kotlin uses nullable types (String?). Swift uses Optional<T>. These make the possibility of absence explicit in the type system — you cannot call methods on a nullable value without first unwrapping it.
Error handling across languages
| Language | Mechanism | Force handling? |
|---|---|---|
| Python | Exceptions (try/except) | No — unchecked |
| Java | Checked + unchecked exceptions | Yes for checked |
| Rust | Result and Option types | Yes — compiler enforces |
| Go | Multiple return (value, error) | Convention only |
| JavaScript | Exceptions + Promise .catch() | No |
| C | Error codes (int return values) | No — easily ignored |
except: pass is not error handling — it is error hiding. Catching without logging, recovering, or re-raising means the program continues with unknown state. Catch only what you know how to handle.try/except KeyError to test dict membership instead of if key in d or d.get(key) is an antipattern — exceptions have overhead and obscure intent.(value, err) but nothing prevents ignoring err. Rust's Result is enforced by the type system — the compiler won't let you use a Result as a value without handling both variants.✅ Intermediate tab complete — check your understanding
- I can explain the difference between checked exceptions (compiler-enforced) and unchecked (Python's approach)
- I understand Go's error-as-value pattern: functions return (result, error) and callers check the error
- I know what Option/Maybe types are in Rust/Haskell: representing "value or nothing" without null
Zero-cost exceptions and algebraic error types
What you'll learn: Zero-cost exception implementations — how modern languages implement exceptions with no overhead unless raised. Algebraic data types for error handling (Rust's Result
How to read this tab: Read when studying language implementation or when working in Rust.
Zero-cost exceptions
In C++ and Rust, exceptions/panics are implemented with zero-cost abstractions: when no exception is thrown, the happy path incurs zero runtime overhead — no condition checks, no status register writes. Exception-handling tables are stored in a separate binary section. When an exception occurs, the runtime walks these tables to find the handler. Cost is paid only on the exceptional path. This contrasts with C-style error codes: checking every return value on the happy path costs cycles even when nothing goes wrong.
Algebraic data types for error handling
Rust's Result<T, E> is a sum type (discriminated union) — either Ok(T) or Err(E). The compiler requires exhaustive matching: a match that doesn't handle both variants is a compile error. This is the strongest static guarantee of forced error handling in any mainstream language — stronger than Java's checked exceptions because it cannot be suppressed with a throws Exception declaration.
Python: Language Reference §8 — Compound statements. Rust: The Rust Book ch09 — doc.rust-lang.org/book. Java: Java Language Specification §11 — Exceptions. Go: go.dev/blog/error-handling-and-go.
✅ Expert tab complete
- I know Python's exceptions have zero overhead on the non-exception path
- I understand Rust's Result
type and the ? operator for propagation