The Language Built for Reliability
Erlang was developed at Ericsson in the late 1980s – with a clear goal: Build telephony systems that must not go down. The requirements back then sound like today’s cloud architecture wish list:
- 99.9999999% availability (the famous “nine nines”)
- Hot code upgrades without downtime
- Thousands of concurrent connections
- Fault tolerance as a core principle
What was originally built for telephone switching is now the foundation for WhatsApp, Discord, RabbitMQ, and many other systems that need to run reliably under extreme load.
1. Lightweight Processes
Erlang processes are neither operating system processes nor threads. They are managed by the BEAM VM and are extremely lightweight:
%% Start a million processes? No problem.
[spawn(fun() -> timer:sleep(60000) end) || _ <- lists:seq(1, 1000000)].
A single Erlang process uses only about 300 bytes of memory. For comparison: a Java thread requires at least 512 KB to 1 MB of stack. That means:
| Erlang Process | Java Thread | Go Goroutine | |
|---|---|---|---|
| Memory | ~300 bytes | ~512 KB | ~8 KB |
| Creation | ~3 µs | ~1 ms | ~6 µs |
| Maximum count | Millions | Thousands | Hundreds of thousands |
2. “Let It Crash” – Errors as a Feature
In most languages, you write defensive code:
// Java: Defensive programming
try {
connection = openConnection(host, port);
data = connection.read();
result = process(data);
} catch (ConnectionException e) {
logger.error("Connection failed", e);
retryWithBackoff();
} catch (DataException e) {
logger.error("Data processing failed", e);
fallbackStrategy();
} catch (Exception e) {
logger.error("Unexpected error", e);
emergencyShutdown();
}
In Erlang, things are fundamentally different. Processes are isolated – when one crashes, it doesn’t affect any other. Supervisors monitor processes and restart them as needed:
%% Supervisor configuration: If a worker crashes, it gets restarted
init([]) ->
SupFlags = #{
strategy => one_for_one, %% Only restart the crashed process
intensity => 5, %% Max 5 restarts ...
period => 60 %% ... per minute
},
Children = [
#{id => worker,
start => {my_worker, start_link, []},
restart => permanent}
],
{ok, {SupFlags, Children}}.
This is not a workaround – this is the architecture. Supervisor trees are structured hierarchically. If an entire subtree is beyond saving, it gets completely restarted without affecting the rest of the system.
3. Pattern Matching – Elegant and Expressive
Pattern matching in Erlang is far more than a switch statement. It permeates the entire language:
%% Function clauses with pattern matching
handle_message({login, Username, Password}) ->
authenticate(Username, Password);
handle_message({send_msg, From, To, Text}) ->
deliver(From, To, Text);
handle_message({logout, Username}) ->
cleanup_session(Username);
handle_message(Unknown) ->
log_warning("Unknown message: ~p", [Unknown]).
No if-else cascades, no type checks. The code reads almost like a specification. And the compiler warns if a case is not covered.
4. Immutable Data – No Race Conditions
In Erlang, all data is immutable. There are no variables that can be overwritten:
%% Once bound, always bound
X = 42,
X = 43. %% Error! "no match of right hand side value 43"
%% Instead: New bindings
List1 = [1, 2, 3],
List2 = [0 | List1]. %% List2 = [0, 1, 2, 3], List1 remains [1, 2, 3]
Combined with process isolation, this means: Race conditions are structurally impossible. No synchronized, no mutex, no volatile. Processes communicate exclusively via message passing:
%% Process A sends a message
PidB ! {self(), "Hello"},
%% Process B receives it
receive
{From, Msg} ->
io:format("Message from ~p: ~s~n", [From, Msg]),
From ! {self(), "Thanks!"}
end.
5. Hot Code Loading – Updates Without Downtime
Erlang can swap running code without stopping the system. This is not a hack – it’s a built-in feature of the BEAM VM:
%% Version 1 is currently running
-module(pricing).
-export([calculate/1]).
calculate(Amount) ->
Amount * 1.19. %% 19% VAT
%% Version 2 is loaded – WITHOUT restart
calculate(Amount) ->
Amount * 1.21. %% New tax rate
Existing connections are not interrupted. New requests use the new code, while in-flight requests complete with the old code. This is why Ericsson switches run for years without a restart.
6. Distribution – Built In, Not Bolted On
Distributed systems are not an add-on package in Erlang. The BEAM VM can natively communicate with other nodes:
%% On Node A
net_adm:ping('nodeB@server2'). %% Establish connection
%% Start a process on another node
Pid = spawn('nodeB@server2', fun() ->
heavy_computation()
end),
%% Send a message to the remote process – same syntax!
Pid ! {process, data}.
Message passing works transparently – regardless of whether the process runs on the same machine or on another continent. The syntax is identical.
7. OTP – The Swiss Army Knife
OTP (Open Telecom Platform) is the real gem. It provides proven building blocks for robust systems:
- GenServer – Generic server process with state management
- Supervisor – Monitoring and restarting of processes
- Application – Packaging and lifecycle management
- ETS – Fast in-memory key-value store
- Mnesia – Distributed database, built in
%% A GenServer in just a few lines
-module(counter).
-behaviour(gen_server).
init([]) -> {ok, 0}. %% Initial state: 0
handle_call(get, _From, Count) ->
{reply, Count, Count}; %% Return current value
handle_cast(increment, Count) ->
{noreply, Count + 1}. %% Increment counter
Who Relies on Erlang?
| Company | Use Case |
|---|---|
| 2 billion users, ~50 engineers | |
| Discord | Millions of concurrent connections |
| RabbitMQ | One of the most popular message brokers |
| Ericsson | Telecom infrastructure worldwide |
| Klarna | Payment platform |
| Nintendo | Push notification system |
WhatsApp is the most impressive example: at the time of its acquisition by Facebook, ~50 developers managed a system serving 900 million users. That ratio is only possible with Erlang.
And What About Elixir?
Elixir runs on the same BEAM VM and offers a more modern syntax with a Ruby-like look:
# Elixir – same power, more modern syntax
defmodule Counter do
use GenServer
def init(_), do: {:ok, 0}
def handle_call(:get, _from, count), do: {:reply, count, count}
def handle_cast(:increment, count), do: {:noreply, count + 1}
end
Elixir additionally brings excellent tooling (Mix, Hex), metaprogramming via macros, and with Phoenix a web framework that offers LiveView for reactive UIs – without JavaScript. For those who want to leverage the BEAM platform, Elixir provides a modern entry point.
Conclusion
Erlang is not the right language for every problem. For numerical computations, GUI applications, or machine learning, there are better tools. But for what it was built for – highly available, distributed, fault-tolerant systems – it remains unmatched after more than 35 years.
The question is not “Why Erlang?” – but rather: “Why are we still building distributed systems without the concepts that Erlang has offered since 1986?”
“If Java is ‘write once, run anywhere’, then Erlang is ‘write once, run forever’.”
— Joe Armstrong, creator of Erlang