DEV Community

Matheus de Camargo Marques
Matheus de Camargo Marques

Posted on Edited on

Practical Erlang/OTP Tutorial Based on Recent Academic Evidence (2025–2026)

Practical Erlang/OTP Tutorial Based on Recent Academic Evidence (2025–2026)

Based on an in-depth search of recent academic articles (2025–2026), I have prepared a practical Erlang/OTP tutorial. The material integrates the main findings from the literature: from refactoring imperative code into the functional paradigm to formal verification of actor systems and the redesign of supervision mechanisms for cooperative runtimes. The tutorial is structured in progressive stages, each grounded in academic evidence.


1. Why Erlang? The Academic Rationale

Modern distributed systems require reliability, scalability, and fault tolerance. Classical imperative languages, with shared memory and explicit threads, often prove inadequate for systems designed for thousands of nodes and millions of concurrent connections. The functional paradigm, which discourages mutable state, offers a cleaner path to parallelization.

Erlang occupies a special place among functional languages because it directly implements the actor model proposed at MIT in the 1970s. In this model, a system consists of processes (actors) that interact exclusively through asynchronous messages. Each actor has a unique address (PID), can create other actors, send messages, and change its behavior. Unlike CSP (Communicating Sequential Processes), which uses synchronous channels, the actor model is better suited to fully decentralized and fault-tolerant systems.

A crucial technical differentiator is memory cost: Erlang’s lightweight processes consume only about 326 words (approximately 2.6 KB on 64-bit systems), compared with 1–8 MB per operating system thread — three orders of magnitude less. This allows the BEAM VM to manage millions of simultaneous processes with efficient resource use.

Supporting reference: STAROLETOV, Sergey. Modern Erlang Teaching from Code Refactoring to Actor System Verification. In: Proceedings of the 25th ACM SIGPLAN International Workshop on Erlang (Erlang '26), 2026. This article synthesizes years of experience teaching Erlang and proposes a methodological progression that goes from refactoring imperative code to verifying actor systems.


2. First Steps: Refactoring from Imperative to Functional

The biggest obstacle for beginners in Erlang is not syntax, but the cognitive shift from imperative to functional thinking. An effective technique proposed in the literature is systematic refactoring of imperative code, eliminating sequential operators (the “commas”) and replacing them with recursion and pattern matching.

Exercise 1: From an imperative loop to a recursive function

Consider the following Java (or Python) code that calculates the sum of the first ( N ) integers:

int sum = 0;
for (int i = 1; i <= N; i++) {
    sum += i;
}
return sum;
Enter fullscreen mode Exit fullscreen mode

In Erlang, the same logic is expressed with tail recursion and pattern matching:

-module(sum).
-export([sum/1]).

sum(N) -> sum(N, 0).

sum(0, Acc) -> Acc;
sum(N, Acc) when N > 0 ->
    sum(N - 1, Acc + N).
Enter fullscreen mode Exit fullscreen mode

The function sum/2 is tail-recursive: the recursive call is the last operation, allowing the BEAM compiler to optimize it into an efficient loop without stack growth.

Academic connection: Staroletov’s article (2026) reports that this approach — removing the “comma mindset” and introducing recursion through Taylor series — transforms students’ understanding of recursion and pattern matching. The author also compares Erlang’s declarative style with Java code laden with object transformations via JInterface, demonstrating how the absence of mutable state simplifies reasoning about concurrent programs.


3. Lightweight Processes and Massive Concurrency

Concurrency in Erlang is not an add-on: it is a first-class citizen. Each process is isolated, with no shared memory, and communication occurs exclusively through asynchronous message passing. This eliminates race conditions and the need for locks and mutexes.

Exercise 2: A TCP server that supports thousands of connections

Staroletov’s article highlights the demonstration of lightweight processes through a TCP server that withstands an enormous number of connections on modest hardware. Let us build a minimal echo server:

-module(echo_server).
-export([start/1]).

start(Port) ->
    {ok, ListenSocket} = gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]),
    accept(ListenSocket).

accept(ListenSocket) ->
    {ok, Socket} = gen_tcp:accept(ListenSocket),
    spawn(fun() -> handle_connection(Socket) end),
    accept(ListenSocket).

handle_connection(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            gen_tcp:send(Socket, Data),
            handle_connection(Socket);
        {error, closed} ->
            ok
    end.
Enter fullscreen mode Exit fullscreen mode

Each accepted connection is handled by a new process created with spawn/1. Since each process consumes only a few kilobytes, tens of thousands of simultaneous connections are viable on a single machine.

Comparison with Java threads: Staroletov (2026) contrasts this approach with Java’s modern virtual threads, showing that although both enable high concurrency, Erlang’s actor model offers fault isolation and hierarchical supervision from the language design onward.


4. OTP: Supervision and Fault Tolerance in Practice

OTP (Open Telecom Platform) is the set of libraries and patterns that makes Erlang an industrial-grade platform. The central concept is supervision: a supervisor process monitors child processes and, when one of them fails, applies a restart strategy (one-for-one, one-for-all, rest-for-one, simple-one-for-one).

Exercise 3: A supervision tree for a counter

Let us create a simple counter server with supervision:

-module(counter).
-behaviour(gen_server).
-export([start_link/0, increment/0, get/0]).
-export([init/1, handle_call/3, handle_cast/2]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

increment() ->
    gen_server:cast(?MODULE, increment).

get() ->
    gen_server:call(?MODULE, get).

init([]) ->
    {ok, 0}.

handle_cast(increment, State) ->
    {noreply, State + 1}.

handle_call(get, _From, State) ->
    {reply, State, State}.
Enter fullscreen mode Exit fullscreen mode

Now the supervisor:

-module(counter_supervisor).
-behaviour(supervisor).
-export([start_link/0, init/1]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    Child = #{id => counter,
              start => {counter, start_link, []},
              restart => permanent,
              shutdown => 5000,
              type => worker,
              modules => [counter]},
    {ok, {{one_for_one, 5, 10}, [Child]}}.
Enter fullscreen mode Exit fullscreen mode

The one_for_one strategy with MaxR = 5 and MaxT = 10 means that if the counter fails more than 5 times in 10 seconds, the supervisor also fails and propagates the error to the next hierarchical level — the “let it crash” principle.

Academic connection: The article Supervision Without Blocking (2026) provides a careful audit of OTP’s supervision guarantees and proposes a redesign for cooperative runtimes. The authors identify 24 classes of defects in supervision implementations that copy the OTP style without understanding its consistency contracts, which presuppose a synchronous, single-threaded supervisor. The article validates the original OTP model but warns that ports to other languages must explicitly reconstruct the atomicity that Erlang’s synchronous supervisor obtains implicitly. This reinforces the importance of using native OTP rather than trying to replicate it superficially.


5. Verification of Actor Systems: Going Beyond Testing

Actor systems are notoriously difficult to verify through traditional testing, given the nondeterminism inherent in message ordering. Recent literature offers two complementary approaches: runtime verification and static protocol verification.

5.1. Runtime Verification with ACTORCHESTRA

The ACTORCHESTRA tool (2025) allows specifying properties that span multiple actors and performing causality-sensitive verification. It defines a specification language, WALTZ, and generates monitoring executables in Erlang, injecting code surgically into the system to preserve causal relationships between actors.

A conceptual example of a verifiable property: “whenever the order actor sends a confirmation message to the customer actor, the inventory actor must have received a reservation message beforehand.” ACTORCHESTRA monitors this causal chain at runtime and alerts if the property is violated.

5.2. Formal Deadlock Detection with DDMon

The article Correct Black-Box Monitors for Distributed Deadlock Detection (2025) presents distributed monitors that detect deadlocks by observing only incoming and outgoing messages, without instrumenting the services’ source code. The formal model captures RPC-based application styles, such as gen_servers in Erlang/OTP, and the monitoring algorithm is proven sound (no false positives) and complete (no false negatives). The DDMon tool implements these monitors for Erlang/OTP applications and demonstrates viable performance.

Practical application in the tutorial: After building the counter system with supervision, you can attach DDMon to monitor for possible deadlocks. Although the counter example is too simple to generate deadlocks, the pattern applies to systems with gen_server processes making chained synchronous calls — a common scenario in microservices.

5.3. Static Protocol Typing with NVLang

The NVLang article (2025) addresses a fundamental limitation of Erlang: the absence of static guarantees about message protocols. Processes communicate by sending arbitrary messages that are filtered by pattern matching at runtime, deferring protocol violations to production failures.

NVLang proposes a statically typed functional language that compiles to Core Erlang, preserving interoperability with the existing ecosystem. The central contribution is that algebraic data types (ADTs) naturally encode actor protocols: each actor declares the sum type that represents its message vocabulary, and the type system enforces conformance at compile time. Typed process identifiers (Pid[τ]) and typed futures (Future[τ]) provide safe request-reply patterns.

Although NVLang is a separate language, the tutorial can use its concepts to discipline protocol design in pure Erlang: before implementing a gen_server, explicitly define the sum type of messages it accepts. This reduces the class of errors that would only manifest in production.


6. Integrative Case Study: A SEDA Web Server in Erlang

The ERLDA article (2025) proposes a scalable and resilient web server using the SEDA (Staged Event-Driven Architecture) with Erlang and OTP. The motivation is that traditional web servers based on synchronous TCP have significant limitations in high-demand scenarios, especially when managing millions of simultaneous connections.

The SEDA architecture decomposes processing into stages connected by event queues, each stage executed by a pool of lightweight processes. In Erlang, this maps naturally to processes communicating through messages:

[ Acceptor ] --> [ Request queue ] --> [ Processor pool ] --> [ Response queue ] --> [ Writer ]
Enter fullscreen mode Exit fullscreen mode

Each arrow represents a process (or pool of processes) that consumes from one queue and produces to the next. OTP supervision ensures that the failure of one stage does not bring down the entire system.

Suggested final exercise: Implement a minimal HTTP server with three stages: (1) an acceptor that receives connections and enqueues requests; (2) a pool of processes that handles the request (for example, generates a simple JSON response); (3) a writer that sends the response back to the client. Use supervisor to ensure that each stage is restarted in case of failure.


7. Conclusion and Study Roadmap

This tutorial started from the academic premise that Erlang is not merely a language, but a computational model for reliable distributed systems. The suggested progression — functional refactoring → lightweight processes → OTP supervision → formal verification → SEDA architecture — is supported by evidence from recent research.

Consolidated roadmap:

  1. Weeks 1–2: Refactoring imperative code into functional code (Exercise 1). Focus on tail recursion, pattern matching, and immutability.
  2. Weeks 3–4: Lightweight processes and message passing (Exercise 2). Comparison with threads and virtual threads.
  3. Weeks 5–6: OTP: gen_server, supervisors, and restart strategies (Exercise 3). Study of OTP consistency guarantees and their limits.
  4. Weeks 7–8: Verification: ACTORCHESTRA for runtime verification and NVLang concepts for protocol typing.
  5. Weeks 9–10: SEDA case study (ERLDA) and construction of a concurrent web server with OTP.

Recent literature confirms that Erlang/OTP remains a reference platform for systems that require “legendary reliability” — from telecommunications to messaging platforms with millions of simultaneous connections. Investing in learning its foundations is therefore not only technically sound but also academically well grounded.


References to the articles used:

  • STAROLETOV, Sergey. Modern Erlang Teaching from Code Refactoring to Actor System Verification. Erlang '26, 2026.
  • UWEJAN, Sam; DOGDU, Erdogan. Supervision Without Blocking: An Evidence-Driven Redesign of OTP-Style Fault Tolerance for Cooperative Runtimes. arXiv:2610.34521, 2026.
  • GUERREIRO, Miguel de Oliveira. NVLang: Unified Static Typing for Actor-Based Concurrency on the BEAM. arXiv:2512.05224, 2025.
  • ROWICKI, Radosław Jan; FRANCALANZA, Adrian; SCALAS, Alceste. Correct Black-Box Monitors for Distributed Deadlock Detection: Formalisation and Implementation. PACMPL, 2025.
  • MIKYTIV, Vladyslav. ACTORCHESTRA: a tool for catching faulty symphonies. Master’s Dissertation, Universidade Nova de Lisboa, 2025.
  • AREIAS, Fernando. ERLDA: Exploring Concurrency and Resilience with SEDA in Erlang. UTFPR, 2025.
  • RIBEIRO, Nuno Dinis Gonçalves. Comparative Analysis of Fault Tolerance in Elixir and Other Distributed and Concurrent Languages. ISEP, 2025.

Top comments (0)