What OTP Supervisors Do
A Supervisor is another OTP behaviour whose entire job is to start, monitor, and restart a fixed set of child processes according to a declared strategy, embodying Erlang's original insight that the way to build reliable software is not to prevent every crash but to detect crashes quickly and recover automatically. A supervisor links to each of its children, so when a child terminates abnormally the supervisor receives an exit signal and can immediately start a fresh replacement, restoring the child to its initial state without any operator intervention.
Cricket analogy: A team's physio watching every fielder from the boundary is like a supervisor -- when a fielder like Ravindra Jadeja goes down with cramp, the physio doesn't try to prevent every possible injury, just spots it instantly and sends on a fresh substitute fielder in the same position.
Restart Strategies: one_for_one, one_for_all, rest_for_one
The restart strategy you pick controls the blast radius of a single child's crash: one_for_one restarts only the child that died, leaving its siblings running untouched, which suits independent children like separate connection-pool workers. one_for_all terminates and restarts every child whenever any one of them crashes, appropriate when children share tightly coupled state that becomes invalid if any piece dies, while rest_for_one restarts the crashed child plus every child started after it in the supervision list, useful when later children depend on earlier ones but not vice versa.
Cricket analogy: one_for_one is like replacing only the injured fielder while the rest of the team continues; one_for_all is like a full team reshuffle after a captain's injury changes tactics for everyone, similar to how Australia rebuilt their whole batting order after a top-order collapse; rest_for_one is like reshuffling only the batting order from the current number down, leaving earlier batsmen's already-completed innings untouched.
defmodule MyApp.Supervisor do
use Supervisor
def start_link(init_arg) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
children = [
{MyApp.ConnectionPool, []},
{MyApp.JobQueue, []},
MyApp.Cache
]
Supervisor.init(children, strategy: :one_for_one, max_restarts: 3, max_seconds: 5)
end
endBuilding a Supervision Tree
Real OTP applications rarely have a single flat supervisor; instead they nest supervisors into a supervision tree where a top-level supervisor's children are themselves supervisors managing their own subtrees of workers, letting you localize the fallout of a crash to just the affected branch of the tree. This tree shape mirrors the application's actual architecture -- for example a web app might have one supervisor for its database connection pool, another for background job workers, and a third for websocket connections, all children of the application's root supervisor.
Cricket analogy: A cricket board's structure with a national selection committee overseeing separate state-level selection panels is like a supervision tree -- a scandal in one state's panel gets contained and reformed at that level without the national committee needing to dissolve every state panel.
Restart Intensity and Child Specs
Supervisors guard against crash loops with a max_restarts and max_seconds window (by default 3 restarts within 5 seconds): if a child keeps crashing faster than that, the supervisor itself gives up, terminates all its children, and exits, propagating the failure up to its own parent supervisor. Each child is described by a child specification -- a map of id, start, restart type (:permanent, :temporary, or :transient), and shutdown behavior -- that tells the supervisor exactly how to start that child and under what conditions to restart it.
Cricket analogy: If a bowler no-balls three times in an over, the umpire escalates and calls in the match referee rather than letting it repeat indefinitely, just as a supervisor gives up and escalates to its own parent after too many restarts in too short a window; and each player's contract specifies terms like whether they're a permanent squad member or a temporary net bowler, mirroring a child spec's restart type.
defmodule MyApp.Worker do
use GenServer, restart: :transient
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
restart: :transient,
shutdown: 5_000
}
end
def start_link(opts), do: GenServer.start_link(__MODULE__, opts)
endChoosing one_for_all or rest_for_one when children don't actually share state is a common mistake -- it needlessly restarts healthy processes and can amplify a small failure into a much larger outage; default to one_for_one unless you can clearly justify the coupling.
- Supervisors start, monitor, and restart child processes according to a declared strategy, embodying let-it-crash recovery.
- one_for_one restarts only the crashed child; one_for_all restarts every child; rest_for_one restarts the crashed child and all children started after it.
- Real applications nest supervisors into supervision trees to localize the blast radius of failures.
- max_restarts and max_seconds bound how many restarts a supervisor tolerates before giving up and escalating to its own parent.
- A child_spec map declares id, start, restart type, and shutdown behavior for each supervised child.
- restart: :permanent always restarts; :temporary never restarts; :transient restarts only on abnormal exit.
- Choosing the right restart strategy prevents both under-recovery and needless over-restarting of healthy processes.
Practice what you learned
1. Which restart strategy restarts only the child process that crashed?
2. When is one_for_all the appropriate restart strategy?
3. What happens when a supervisor exceeds its max_restarts within max_seconds?
4. What does a child_spec's restart: :transient setting mean?
5. What is the purpose of nesting supervisors into a supervision tree?
Was this page helpful?
You May Also Like
GenServer Explained
Understand how GenServer standardizes stateful server processes in Elixir with a clean client/callback API.
Fault Tolerance in Elixir
Explore how links, monitors, and the let-it-crash philosophy combine to make Elixir systems resilient by design.
Processes in Elixir
Learn how Elixir's lightweight BEAM processes enable massive concurrency through isolation and message passing.
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