Error Handling

How different programming languages model and propagate failure.

JavaScript

Uses throw/try/catch/finally, and any value can technically be thrown, though Error objects are conventional. Asynchronous failures propagate as rejected Promises, handled via .catch() or try/catch around await. There are no checked exceptions.

Python

try/except/else/finally handles exceptions, which form a hierarchy rooted at BaseException. Custom errors are defined by subclassing Exception, and context managers (with) are the idiomatic way to guarantee cleanup.

TypeScript

Shares JavaScript’s runtime try/catch model exactly; the type system does not track which errors a function might throw, so typed error handling relies on conventions like returning tagged union “result” types.

Java

Distinguishes checked exceptions (must be declared or caught) from unchecked ones, all handled with try/catch/finally. Try-with-resources automatically closes resources, and exceptions form a hierarchy under Throwable.

C#

Uses try/catch/finally with all exceptions unchecked, organized under a hierarchy rooted at System.Exception. The using statement ensures disposable resources are cleaned up even when an exception occurs.

C++

Supports exceptions (throw/try/catch) with stack unwinding, commonly paired with RAII for automatic cleanup. noexcept documents non-throwing functions, and error-code-based alternatives (like std::expected) are also used, especially in performance-sensitive code.

C

Has no exception mechanism. Failures are communicated through return codes and the global errno; setjmp/longjmp provide a rarely-used non-local jump mechanism for exceptional control flow.

PHP

Supports exceptions (throw/try/catch/finally) under the Throwable interface, which covers both Exception and Error since PHP 7. Multiple catch blocks and multi-type catches are supported; some failures still surface as separate warnings/notices rather than exceptions.

Go

Ordinary errors are values: functions return an (result, error) pair that callers check explicitly rather than catching exceptions. panic/recover exist but are reserved for truly unexpected, unrecoverable situations.

Rust

Recoverable errors are modeled with Result<T, E>, propagated concisely with the ? operator. panic! is reserved for unrecoverable errors, typically unwinding the stack (or aborting, depending on configuration). There are no exceptions.

Kotlin

Has only unchecked exceptions — none must be declared — handled with try/catch/finally, which can also be used as an expression. Nullable types and sealed classes (or a Result type) are often favored over exceptions for expected failure cases.

Ruby

Exceptions are raised with raise and handled with rescue/ensure, with retry allowing a rescued block to be re-attempted. Most errors descend from StandardError, and rescue clauses can target specific exception classes.

Dart

Uses exceptions (try/catch/throw) plus Future/async error propagation; any object can be thrown, though Exception and Error subtypes are conventional. Async errors surface through a rejected Future or a thrown error inside an async function, catchable with try/catch around await or .catchError().

Swift

Functions that can fail are marked throws and called with try inside a do/catch block, with errors conforming to the Error protocol. Result<Success, Failure> offers an explicit alternative for representing success or failure as a value.

Perl

Traditionally uses die/eval for exception-like handling, where die raises an error (often a string or object) and eval catches it by checking $@ afterward. Modules like Try::Tiny and Try::Catch provide cleaner, more structured try/catch syntax on top of this.

Elixir

Favors returning {:ok, value}/{:error, reason} tuples for expected failure and embraces “let it crash” for the unexpected, relying on supervisors to restart failed processes. raise/rescue/try exist for truly exceptional cases, and process exits can propagate via links.

Scala

Supports exceptions plus idiomatic functional alternatives: Option for optional values, Either for two-way results, and Try for wrapping code that may throw. Pattern matching over these types makes handling failure explicit and composable rather than relying on control flow.

Clojure

Uses JVM exceptions (try/catch/throw/finally) since it runs on the JVM, but enriches them with ex-info, which attaches an arbitrary map of structured data to an exception. ex-data retrieves that map in a catch block, giving exceptions richer, inspectable context.

Haskell

Avoids exceptions in pure code, instead favoring Maybe for optional results and Either for computations that can fail with an error value, composed via the type system rather than control flow. Exceptions still exist for IO, handled through Control.Exception with functions like catch and try.