Three Kinds of Exceptions
Erlang has three distinct classes of exception, all raised and caught through the same mechanism but carrying different intent: error/1 signals a genuine bug or unexpected condition (like a failed pattern match, which itself raises a {badmatch, Value} error, or a badarith, badarg, or function_clause error the runtime generates automatically), exit/1 signals that a process wants to terminate, whether normally or abnormally, and throw/1 is a non-local return mechanism used for ordinary control flow, such as escaping early from a deeply nested computation.
Cricket analogy: A batter given out for hitting their own wicket (a genuine mistake, like error/1), a player retiring hurt mid-innings (a deliberate exit, like exit/1), and a captain calling for a strategic timeout to reset momentum (a planned interruption, like throw/1) are three distinctly different reasons play might stop, mirroring Erlang's three exception classes.
try...catch and the catch Operator
The try Expr of Pattern -> Body catch Class:Reason:Stacktrace -> Handler end construct is the modern (OTP 21+) way to both run code and handle any of the three exception classes it might raise, where Class is bound to error, exit, or throw so a single catch clause can distinguish which kind of exception occurred, and the optional Stacktrace variable, retrievable this way instead of the older erlang:get_stacktrace/0, lets you log or re-raise the original error context. The older, terser catch Expr operator is still common for simple cases: it evaluates Expr and, if an exception occurs, returns {'EXIT', Reason} for error and exit, or Reason directly for a throw, but because it collapses all three classes into ambiguous return shapes, catch is best reserved for situations where you genuinely don't care which kind of exception happened.
Cricket analogy: A stump microphone that separately tags an umpire's decision, a player's own comment, and crowd noise into three distinct labeled channels is like try...catch's Class:Reason:Stacktrace distinguishing error, exit, and throw, whereas an old single unlabeled broadcast feed that just says 'something happened' is like the terser catch operator's ambiguous {'EXIT', Reason} shape.
-module(error_demo).
-export([safe_divide/2, demo/0]).
safe_divide(A, B) ->
try A / B of
Result -> {ok, Result}
catch
error:badarith:Stacktrace ->
{error, {badarith, Stacktrace}};
error:badarg ->
{error, badarg}
end.
demo() ->
{ok, 2.5} = safe_divide(5, 2),
{error, {badarith, _}} = safe_divide(5, 0),
%% "Let it crash": a worker that just dies on bad input, trusting a
%% supervisor to restart it, instead of defensively handling every case.
process_flag(trap_exit, true),
Worker = spawn_link(fun() -> 1 = 2 end),
receive
{'EXIT', Worker, Reason} ->
io:format("Worker crashed as expected: ~p~n", [Reason])
after 1000 ->
io:format("No exit received~n")
end.Since Erlang/OTP 21, the recommended catch form is Class:Reason:Stacktrace, which binds the stacktrace explicitly in the catch clause; the older erlang:get_stacktrace/0 function it replaced was error-prone because the stacktrace could be silently clobbered by intervening code before you read it.
The 'Let It Crash' Philosophy
Erlang's most distinctive error-handling idea is 'let it crash': rather than wrapping every risky operation in defensive try...catch blocks, idiomatic Erlang code often lets a process simply die when it hits an unexpected condition, trusting a supervisor to notice the crash and restart the process into a known-good state. This works because Erlang processes are cheap and isolated, a crash in one process cannot corrupt another process's memory, and restarting a process from scratch is usually both simpler and more reliable than trying to enumerate every possible failure mode and defensively code around each one, especially for transient failures like a database connection blip that a fresh process will simply not encounter on its next attempt.
Cricket analogy: A T20 franchise doesn't rebuild its entire strategy around one dismissal; it sends the next batter in fresh rather than trying to defend against every conceivable way a wicket could fall, the same 'restart rather than defend everything' philosophy 'let it crash' embodies by trusting a supervisor to restart a failed process cleanly.
Reach for try...catch only around operations whose failure you can meaningfully recover from right there (like a malformed external input you can reject with a clean error tuple), wrapping broad swaths of business logic in catch-all try...catch blocks defeats 'let it crash' and tends to hide bugs that a supervisor restart would have surfaced and fixed cleanly.
Linking, Monitoring, and Supervisors
Supervision trees are how 'let it crash' becomes a real fault-tolerance strategy rather than just chaos: a process can link/1 to another so that if either one crashes abnormally, the linked process receives an exit signal too (which, by default, kills it in turn, propagating the failure), or it can set process_flag(trap_exit, true) to convert those exit signals into ordinary messages it can inspect and react to instead of dying, this is precisely the mechanism a supervisor behavior uses internally to notice when a child process has crashed and restart it according to a configured strategy (one_for_one, one_for_all, rest_for_one), without the programmer having to hand-roll any of that detection and restart logic themselves.
Cricket analogy: A team's fielding chain reacts as one unit, if the wicketkeeper misses a signal the slip cordon adjusts automatically, mirroring how linked Erlang processes propagate a failure signal, while the vice-captain (like a trap_exit process) absorbs bad news calmly instead of reacting instinctively.
- Erlang has three exception classes, error (bugs/unexpected conditions), exit (process termination), and throw (non-local control flow), all raised and caught through the same mechanism.
- try...catch (with the modern Class:Reason:Stacktrace form) lets you handle each exception class distinctly; the older catch operator collapses them into an ambiguous {'EXIT', Reason} or bare Reason.
- 'Let it crash' means idiomatic Erlang often skips defensive error handling in favor of letting a process die and letting a supervisor restart it into a known-good state.
- This works because processes are cheap, isolated, and share no memory, so one process's crash cannot corrupt another's state.
- link/1 propagates exit signals between linked processes by default; process_flag(trap_exit, true) converts those signals into ordinary messages a process can react to.
- Supervisor behaviors use linking and trap_exit internally to detect a child's crash and restart it according to a configured strategy (one_for_one, one_for_all, rest_for_one).
Practice what you learned
1. Which of the three Erlang exception classes is generated automatically by a failed pattern match?
2. What is the main practical difference between try...catch and the older catch operator?
3. What is the core idea behind Erlang's 'let it crash' philosophy?
4. What does process_flag(trap_exit, true) change about how a process handles a linked process's crash?
5. Why is 'let it crash' considered safe as a systemic strategy in Erlang, unlike in many other languages?
Was this page helpful?
You May Also Like
Records in Erlang
Records give named, compile-time-checked structure to tuples, making Erlang code more readable and less error-prone when working with structured data.
ETS Tables
ETS (Erlang Term Storage) provides fast, in-memory tables for sharing large amounts of data between processes without the overhead of message passing.
Binary Pattern Matching
Erlang's bit syntax lets you construct and deconstruct binary data with precise, declarative pattern matches, making it a natural fit for parsing network protocols and binary file formats.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics