Agency

Building Fault-Tolerant Architectures with OTP and Supervisors

How Erlang/OTP provides a practical foundation for building resilient distributed systems—using processes, supervision trees, isolation, controlled restarts, and “let it fail” principles to keep applications available even when individual components break.

LAST UPDATED: January 19, 2026
6 min read
Building Fault-Tolerant Architectures with OTP and Supervisors

How Erlang/OTP provides a practical foundation for building resilient distributed systems—using processes, supervision trees, isolation, controlled restarts, and “let it fail” principles to keep applications available even when individual components break.

Why Fault Tolerance Matters in Modern Systems

Modern applications are expected to remain available even when individual components fail.

That sounds straightforward until a production system starts experiencing real failures:

  • A process crashes
  • A database connection disappears
  • A network request times out
  • A dependency becomes unavailable
  • A message arrives in an unexpected format
  • Memory usage grows unexpectedly
  • A node becomes unreachable

In many application architectures, one component failing can trigger a chain reaction:

Component A
    ↓
Failure
    ↓
Component B
    ↓
Failure
    ↓
Component C
    ↓
System Degradation

Erlang/OTP takes a different approach.

Instead of assuming components will never fail, the platform is designed around the idea that failures should be isolated, detected, and recovered automatically.

The architecture becomes:

Component
    ↓
Failure
    ↓
Supervisor Detects
    ↓
Restart
    ↓
System Continues

This is the foundation of fault-tolerant OTP systems.

What Makes OTP Different?

OTP is more than a collection of Erlang libraries.

It provides established patterns for building systems that need to remain operational despite failures.

The key concepts include:

Processes

Supervisors

GenServer

Applications

Supervision trees

Behaviours

Fault isolation

The central philosophy can be summarized as:

Let individual components fail without allowing the entire system to fail.

Instead of writing complicated recovery logic into every component, OTP provides a structured way to organize failure detection and recovery.

Understanding the Erlang Process Model

Erlang processes are lightweight execution units designed to operate independently.

They communicate primarily through message passing:

Process A
    │
    │ Message
    ▼
Process B

This provides strong isolation between application components.

If one process crashes, other processes do not automatically crash with it.

For example:

                Application
                    │
       ┌────────────┼────────────┐
       ▼            ▼            ▼
   Process A     Process B     Process C
       │            │            │
    Crash!          │            │
       X            │            │
                    │            │
             Continue Running

That isolation is one of the reasons OTP can build highly resilient systems.

Instead of treating the entire application as one execution unit, the architecture divides work into many isolated processes.

Supervisors: The Foundation of Recovery

A supervisor is responsible for monitoring child processes.

Conceptually:

             Supervisor
                  │
        ┌─────────┼─────────┐
        ▼         ▼         ▼
     Worker A   Worker B   Worker C

If a child process terminates unexpectedly:

Worker B
   ↓
Crash
   ↓
Supervisor
   ↓
Restart Worker B

The supervisor does not necessarily try to understand why every failure happened.

Its primary responsibility is to enforce the recovery strategy defined for its children.

This creates a clean separation:

Workers do the work.

Supervisors manage failure.

That separation is extremely powerful.

Designing Supervision Trees

Real applications rarely have one supervisor controlling every process.

Instead, OTP applications are commonly structured as supervision trees.

For example:

                  Root Supervisor
                        │
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
     Web Supervisor  Data Supervisor  Job Supervisor
          │             │             │
       ┌──┴──┐        ┌─┴──┐        ┌─┴──┐
       ▼     ▼        ▼    ▼        ▼    ▼
     API   Cache     DB   Worker    Job1 Job2

This creates multiple recovery boundaries.

If a background job crashes, the system does not necessarily need to restart the API layer.

If a cache process fails, unrelated workers can continue operating.

The supervision tree becomes a map of failure boundaries.

Let It Fail: A Different Approach to Reliability

One of OTP's most important ideas is often summarized as:

Let it fail.

This does not mean ignoring errors.

It means avoiding excessive defensive complexity inside every process.

Imagine a worker encounters corrupted state.

Instead of trying to recover every possible internal condition:

Worker
  ↓
Unexpected State
  ↓
Complex Recovery Logic
  ↓
More Unexpected State
  ↓
More Complexity

OTP can take a simpler approach:

Worker
  ↓
Unexpected State
  ↓
Crash
  ↓
Supervisor
  ↓
Clean Restart

The restarted process can initialize from a known state.

This approach can make failure handling more predictable.

Of course, not every failure should result in an immediate restart.

The restart strategy needs to match the workload.

Choosing the Right Restart Strategy

OTP provides different supervision strategies for different failure relationships.

A key distinction is whether child processes are independent or related.

For independent workers:

Worker A crashes
      ↓
Restart Worker A

For tightly coupled workers:

Worker A
Worker B
Worker C
   │
   ▼
Shared State / Dependency

a failure in one process may justify restarting a broader group.

Supervision strategies can therefore define whether to:

Restart only the failed child

Restart related children

Restart the entire group

The architecture should reflect the actual dependency model.

The important question is:

What is the smallest unit that can be safely restarted?

That is a fundamental fault-tolerance design decision.

Handling Failures Without Cascading

A good supervision tree prevents local failures from becoming system-wide failures.

Consider:

Payment Worker
     ↓
Failure
     ↓
Payment Supervisor
     ↓
Restart

while:

API
 │
 ├── Customer Service
 ├── Catalog
 └── Background Jobs

continues operating.

This is fault isolation.

The system does not need to pretend that failures never happen.

It needs to ensure that failures remain within controlled boundaries.

A useful principle is:

Contain the failure before attempting to recover from it.

Building Stateful Services with OTP

OTP's `GenServer` behaviour is commonly used to structure stateful processes.

Conceptually:

             GenServer
                 │
       ┌─────────┼─────────┐
       ▼         ▼         ▼
     State     Calls     Messages

A GenServer can maintain state and respond to synchronous or asynchronous messages.

For example:

Client
  │
  │ Request
  ▼
GenServer
  │
  ├── Read State
  ├── Update State
  └── Reply

If the process crashes, its supervisor can restart it.

This provides a useful combination:

Encapsulated state + process isolation + supervised recovery

For more complex state, the system still needs a durable source of truth where appropriate.

A restart should not accidentally mean permanent data loss.

Fault Tolerance in Distributed Systems

OTP becomes even more interesting when applications run across multiple nodes.

A distributed architecture may look like:

             Cluster
        ┌──────┴──────┐
        ▼             ▼
      Node A        Node B
        │             │
    Processes      Processes
        │             │
        └──────┬──────┘
               ▼
          Distributed
           System

Now the system needs to handle:

Node failures

Network partitions

Message delays

Process failures

Service restarts

Distribution increases the number of possible failure scenarios.

OTP provides mechanisms and patterns for distributed communication, but application architects still need to reason carefully about consistency, availability, network partitions, and external dependencies.

Supervision solves process recovery.

It does not magically solve every distributed-systems problem.

Observability and Operational Control

Automatic recovery is valuable, but teams still need to understand why processes are failing.

A production OTP system should provide visibility into:

Process crashes

Restart frequency

Supervisor activity

Memory usage

Message queues

Latency

External dependency failures

A useful operational model is:

Application
    │
    ▼
Telemetry
    │
 ┌──┼──────────┐
 ▼  ▼          ▼
Logs Metrics  Traces
    │
    ▼
Observability
    │
    ▼
Engineering Team

A process that restarts once may be normal.

A process that restarts hundreds of times per minute is a signal of a deeper problem.

This is why restart activity should be observable rather than silently ignored.

Common OTP Architecture Mistakes

Creating Huge Supervisors

A supervisor with too many unrelated responsibilities can become difficult to reason about.

Group processes around meaningful failure boundaries.

Restarting Too Much

Restarting an entire subsystem because one worker failed can create unnecessary disruption.

Use the smallest safe recovery unit.

Restarting Too Little

The opposite problem is also possible.

If multiple processes depend on shared state, restarting only one may leave the subsystem inconsistent.

Understand the dependency relationship.

Treating Restarts as Data Recovery

A process restart restores execution—not necessarily business state.

Persist important state in durable systems where required.

Ignoring Restart Loops

A process that immediately crashes after every restart can create excessive resource consumption.

Supervision policies need appropriate restart intensity controls.

Using OTP Without Understanding the Failure Model

Supervisors are powerful, but they cannot replace good architecture.

You still need to understand:

Dependencies

State

Consistency

External services

Network failures

A Practical Fault-Tolerant Design Strategy

Step 1: Identify Failure Boundaries

Map the components that can fail independently.

Step 2: Isolate Responsibilities

Use lightweight processes with clear ownership.

Step 3: Define Supervisors

Create supervision trees around meaningful subsystems.

Step 4: Choose Restart Strategies

Determine whether a failure requires:

One process

A group

An entire subsystem

to restart.

Step 5: Keep State Recoverable

Use durable storage for important business state.

Step 6: Design for External Failures

Assume databases, APIs, networks, and third-party services will sometimes fail.

Step 7: Add Observability

Track crashes, restarts, queues, latency, and resource usage.

Step 8: Test Failure Deliberately

Simulate:

Process crashes

Dependency failures

Network interruptions

Node failures

Unexpected messages

Step 9: Verify Recovery

A system is not fault tolerant simply because it can restart.

Verify that it actually returns to a healthy state.

The Future of OTP-Based Systems

As distributed applications become more demanding, the principles behind OTP remain highly relevant.

Modern systems increasingly require:

Continuous availability

Automatic recovery

Fault isolation

Distributed execution

Real-time processing

These are exactly the types of problems OTP was designed to address.

The architecture can evolve toward:

                 Application
                      │
                Supervision
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
   Services         Workers        Processes
       │              │              │
       └──────────────┼──────────────┘
                      ▼
               Distributed Runtime
                      │
                Observability

The broader industry is also increasingly embracing concepts that fit naturally with OTP:

Graceful degradation

Automatic recovery

Isolation

Backpressure

Supervised workloads

Failure-aware design

The technology may change, but the architectural principle remains powerful:

Systems should be designed around the assumption that components will fail.

Making the Call

Engineering leaders considering OTP should ask:

How much availability does the system require?

Which components can fail independently?

What is the smallest safe unit of recovery?

Which state must survive a process restart?

What happens when an external dependency becomes unavailable?

Can the team observe and understand automatic restarts?

Have failure scenarios actually been tested?

OTP is particularly compelling for systems where uptime, concurrency, fault isolation, and continuous operation are core requirements.

But the technology should still be selected based on workload and team expertise.

Final Takeaway

The strength of OTP is not simply that Erlang processes are lightweight.

It is the way the runtime, processes, behaviours, and supervisors work together to create a failure-aware architecture.

The fundamental model is:

Isolate → Supervise → Fail Safely → Restart → Recover

Processes isolate work.

Supervisors monitor processes.

Supervision trees define recovery boundaries.

Restart strategies determine how much of the system should be recovered.

Observability tells engineers what is actually happening.

Together, these patterns allow applications to remain operational even when individual components fail.

Fault tolerance is not about preventing every failure. It is about preventing individual failures from becoming system-wide disasters.

That is the real power of OTP.

Instead of building an application around the assumption that everything will work, you build it around a more realistic assumption:

Something will fail. The system should know what failed, contain the damage, recover automatically, and keep serving users.

For systems where continuous availability matters, that mindset can be more valuable than any individual framework or infrastructure technology.

Frequently Asked Questions

No. 'Let it fail' means avoiding highly complex, defensive try/catch blocks that attempt to handle corrupted internal state. Instead of continuing with unpredictable state, the process immediately crashes and its supervisor restarts it from a known, clean, and reliable state.
Erlang processes are managed entirely by the BEAM virtual machine. They are incredibly lightweight (taking only a few kilobytes of memory) and take microseconds to spin up. A single machine can easily run hundreds of thousands or even millions of concurrent Erlang processes.
It can, if you treat the GenServer's internal memory as your only persistence layer. A restart drops the current state. Critical business state must be backed by a durable data store (like a database) so that when the GenServer restarts, it can safely repopulate its state.
Supervisors use a mechanism called 'Maximum Restart Intensity' (e.g., max 3 restarts in 5 seconds). If a child process exceeds this limit, the supervisor gives up and terminates itself. This failure escalates up the supervision tree to prevent infinite crash-restart loops.

Need a product built?

We build custom software, mobile apps, and web platforms for startups and enterprises.

Alejandro D.
Vatsalya R.Backend Developer
Gustavo A.
Ganeshan S.Sr. Software Engineer
Fiorella G.
Uptal JoshiSr. Data Scientist

Their team became an extension of ours — within months they'd rebuilt our entire product experience from the ground up.

BitForge
Sr. ArchitectBitForge
Read Case Study