The Let It Crash Philosophy
Elixir's fault-tolerance philosophy, inherited from Erlang, deliberately encourages writing 'happy path' code and letting unexpected errors crash the process rather than defensively coding around every conceivable edge case, because a crashed process that gets cleanly restarted by a supervisor is often more reliable than a process that limped on in a corrupted, half-handled state. This doesn't mean ignoring errors you can meaningfully recover from locally -- it means reserving try/rescue for truly expected, recoverable conditions and trusting supervision for everything else.
Cricket analogy: A bowler who oversteps and gets a no-ball called doesn't try to secretly finish the delivery as if it were legal -- the umpire stops it cleanly and the bowler restarts the run-up fresh, which is far safer than play continuing on a delivery everyone knows was compromised, like a phantom Mitchell Starc no-ball wicket being overturned rather than left standing.
Links vs Monitors
A link, created implicitly by spawn_link/1,3 or explicitly by Process.link/1, creates a bidirectional relationship where if either linked process crashes, the other receives an exit signal too -- by default that exit signal kills the linked process unless it's trapping exits, which is exactly the mechanism supervisors rely on to learn when a child has died. A monitor, created with Process.monitor/1, is one-directional and non-lethal: the monitoring process receives a {:DOWN, ref, :process, pid, reason} message when the monitored process terminates, but its own execution is never interrupted, making monitors the right tool when you need to observe a process without being tied to its fate.
Cricket analogy: Two batsmen at the crease are linked -- if one gets run out attempting a risky second run, both their partnership and momentum are affected together, whereas a commentator watching the match from the box is like a monitor, getting notified the instant a wicket falls without their own broadcast being interrupted by it.
# Linked: if worker crashes, this process is also terminated (unless trapping exits)
worker_pid = spawn_link(fn -> risky_work() end)
# Monitored: this process just gets a message, it is not terminated
{monitor_pid, ref} = spawn_monitor(fn -> risky_work() end)
receive do
{:DOWN, ^ref, :process, ^monitor_pid, reason} ->
IO.puts("Worker went down: #{inspect(reason)}")
endTrapping Exits and Custom Recovery
A process can call Process.flag(:trap_exit, true) to convert incoming exit signals from links into ordinary {:EXIT, pid, reason} messages delivered to its mailbox instead of being killed outright, which is precisely how Supervisor itself stays alive when a child dies -- it traps exits, receives the {:EXIT, ...} message, and decides how to react according to its restart strategy rather than crashing alongside its child. Outside of writing supervisor-like infrastructure yourself, trapping exits is relatively rare in application code, since OTP's built-in Supervisor already handles this pattern correctly and battle-tested.
Cricket analogy: A team manager who chooses to personally review every player injury report instead of automatically substituting is like trap_exit -- rather than the team being pulled apart the moment a player goes down, the manager gets a full report and decides the right response, exactly how a captain like Eoin Morgan might manage an injury with more nuance than an automatic withdrawal.
defmodule SimpleWatcher do
def start do
Process.flag(:trap_exit, true)
child = spawn_link(fn -> :timer.sleep(1_000); exit(:boom) end)
loop(child)
end
defp loop(child) do
receive do
{:EXIT, ^child, reason} ->
IO.puts("Child exited with reason: #{inspect(reason)}, restarting...")
new_child = spawn_link(fn -> :timer.sleep(1_000); exit(:boom) end)
loop(new_child)
end
end
endDefensive Code vs Supervised Recovery
The discipline of 'let it crash' doesn't mean skipping input validation or reasonable error handling -- a function that receives obviously bad user input should still return a clear {:error, reason} tuple that its caller can pattern-match on, because that's an expected, everyday outcome, not an exceptional crash-worthy condition. What you skip is the defensive sprawl of checking for every conceivable database-connection failure, network timeout, or unexpected nil deep inside business logic; instead you let those genuinely exceptional cases raise, trust the supervisor to notice and restart, and keep your core logic focused on the actual happy-path problem you're solving.
Cricket analogy: A batsman leaving a ball outside off stump is a normal, expected outcome handled routinely, not a crisis -- but a freak equipment failure like a bat handle snapping mid-shot is a genuine exceptional event that stops play entirely rather than being quietly worked around.
Never blanket-wrap large chunks of business logic in try/rescue just to 'be safe' -- swallowing exceptions defeats the supervisor's ability to detect real failures and often leaves a process limping along in a state its own code never anticipated, which is usually worse than the crash you were trying to avoid.
- Let it crash means writing happy-path code for expected logic and trusting supervision for truly exceptional failures.
- Links are bidirectional and lethal by default: a crash in either linked process propagates to the other.
- Monitors are one-directional and non-lethal: the monitor receives a {:DOWN, ...} message without being terminated.
- Process.flag(:trap_exit, true) converts incoming exit signals into ordinary {:EXIT, ...} messages instead of killing the process.
- Supervisor itself relies on trapping exits to detect child crashes and apply its restart strategy.
- Expected, recoverable conditions should return explicit {:error, reason} tuples rather than crashing.
- Genuinely exceptional failures (bad connections, unexpected nils deep in logic) should be allowed to raise and be handled by supervision.
Practice what you learned
1. What is the core idea behind Elixir's 'let it crash' philosophy?
2. What is the key difference between a link and a monitor?
3. What does Process.flag(:trap_exit, true) do?
4. How does Supervisor use trap_exit internally?
5. According to the let-it-crash philosophy, how should an expected, recoverable error like invalid user input be handled?
Was this page helpful?
You May Also Like
Supervisors and OTP
Learn how OTP supervisors monitor child processes and restart them automatically to build fault-tolerant systems.
Processes in Elixir
Learn how Elixir's lightweight BEAM processes enable massive concurrency through isolation and message passing.
GenServer Explained
Understand how GenServer standardizes stateful server processes in Elixir with a clean client/callback API.
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