History of Gleam and BEAM
BEAM evolved around lightweight isolated processes and fault recovery; Gleam adds static types and a compact functional language without replacing that runtime model.
Gleam and BEAM solve different parts of the same problem. Gleam gives source code a statically checked functional language with algebraic data types, exhaustive pattern matching, and inferred types. BEAM belongs to the Erlang runtime system that executes the resulting program with lightweight processes, message passing, scheduling, and fault isolation.
Gleam source
↓ type checking and code generation
Erlang source
↓ Erlang compiler
BEAM module
↓ loaded by ERTS
scheduled processes, mailboxes, links, and supervisors
The relationship is architectural rather than accidental. Gleam did not reproduce Erlang’s concurrency runtime. It placed a different language boundary over machinery refined through decades of telecommunications systems.
Erlang Begins with Concurrency
Ericsson’s Computer Science Laboratory experimented with more than twenty languages for telecommunications between 1982 and 1985. Telephone switching software needed large numbers of activities to proceed independently, recover from faults, and continue operating while the system was being changed. A process in the language therefore had to correspond closely to one concurrent activity in the domain.
The first Erlang implementation was written in Prolog in 1986. It was deliberately easy to change, which made it useful for discovering the language, but it was too slow for production systems. Erlang’s durable model emerged from those experiments:
process = private state + mailbox + current computation
Processes do not share ordinary mutable memory. They communicate by sending immutable terms asynchronously. A failure terminates one process with a reason; links and monitors turn that failure into a signal that another process can handle. This made failure part of the program’s control structure instead of an exceptional condition hidden below it.
From Prolog to JAM, TEAM, and BEAM
The runtime passed through several abstract machines as Ericsson tried to make the process model fast enough for real products.
JAM, Joe’s Abstract Machine, arrived in 1989. Mike Williams wrote its runtime in C, Joe Armstrong wrote the compiler, and Robert Virding wrote the libraries. It ran Erlang roughly seventy times faster than the Prolog interpreter, but larger telecommunications systems still demanded more performance.
Bogumil “Bogdan” Hausman’s TEAM, the Turbo Erlang Abstract Machine, compiled Erlang through C to native machine code. Small programs ran faster, but compilation was slow and generated code was too large for major systems. BEAM followed as a hybrid: ordinary modules could use compact threaded interpreter code while performance-critical modules could be compiled through C.
Prolog interpreter → JAM bytecode → TEAM native code via C → BEAM hybrid machine
The tradeoff favoured a virtual machine again. Compact code, fast loading, and predictable execution mattered more to large continuously running systems than maximizing the speed of isolated functions.
BEAM, ERTS, and OTP Are Different Layers
“BEAM” is often used as a name for the whole Erlang platform, but the implementation has three distinct layers:
- BEAM is the register machine and instruction format that executes compiled modules.
- ERTS, the Erlang Runtime System, supplies processes, schedulers, garbage collection, timers, ports, distribution, and other runtime services.
- OTP, the Open Telecom Platform, supplies libraries and design patterns such as applications, generic servers, and supervision trees.
The distinction explains why languages other than Erlang can inherit the platform. A compiler needs to produce code compatible with the runtime, while libraries can build higher-level process structures without changing the VM instruction set.
The OTP group was created to industrialize Erlang and make the complete system suitable for large products. OTP R1B shipped in 1996. Erlang/OTP was released as open source in 1998, moving a platform developed inside Ericsson into a public ecosystem.
The New BEAM
Early BEAM accumulated more than three hundred changing instructions and several separate native-via-C implementations. Every instruction affected the compiler, loader, and interpreter, so performance work also multiplied maintenance cost.
OTP R5 introduced the modern BEAM file format and a much smaller instruction set. The threaded interpreter had become fast enough to remove the C backend, and the loader could translate a stable external instruction format into the runtime’s internal representation. OTP R6 then adopted a new compiler pipeline based on Kernel Erlang while continuing to target the same machine.
BEAM is a register machine. Temporary X registers pass arguments and hold short-lived values; Y registers occupy a process’s stack frame. A compiled .beam file contains encoded instructions and metadata for one module. The loader validates and transforms those instructions before execution.
That instruction engine is only one part of the concurrency model. ERTS schedules many Erlang processes over scheduler threads, normally across all available CPU cores. Each process has its own stack and heap, so most garbage collection pauses one process rather than the entire system. Messages enter the receiving process’s mailbox, and selective receive chooses the first message matching a pattern.
sender A ──message──▶ mailbox B ──pattern match──▶ process B
Isolation makes failure containment practical, but it is not free. Messages usually copy their data, mailboxes can grow without bound, and a process that handles messages too slowly creates back pressure in memory rather than automatically slowing its senders.
Fault Tolerance Becomes Structure
OTP turned Erlang’s process primitives into repeatable architecture. Workers perform application work. Supervisors observe workers and restart them according to a declared strategy. Supervisors can themselves be supervised, producing a tree whose branches define fault boundaries.
supervisor
├── worker
├── worker
└── supervisor
├── worker
└── worker
“Let it crash” does not mean ignoring errors. It means keeping corrupt local state inside a process, allowing that process to terminate, and delegating recovery to a separate process with known-good state and an explicit restart policy. The approach depends on isolation; restarting a thread that has mutated shared memory would not restore a reliable boundary.
BEAM continued to change under this higher-level model. Erlang/OTP 24 added BeamAsm in 2021, translating BEAM instructions to native code as modules load. The JIT removed interpreter dispatch overhead without replacing BEAM’s registers, process scheduling, or module format. Runtime performance improved while the concurrency semantics remained stable.
Gleam Adds a Typed Language Boundary
Gleam’s first numbered release, v0.1, appeared in April 2019. Its initial proposition combined Erlang’s actor-oriented runtime with the kind of sound, inferred type system associated with ML-family languages. The compiler prototypes were written in Erlang, then rewritten in Rust because static types made large compiler refactors safer and the rewrite removed accumulated implementation problems.
Gleam types ordinary data before it reaches the runtime:
pub type User {
LoggedIn(name: String)
Guest
}
pub fn greeting(user: User) -> String {
case user {
LoggedIn(name) -> "Welcome back, " <> name
Guest -> "Hello"
}
}
User has two possible shapes, and the compiler requires the case expression to cover both. There is no unchecked null state between them. Type inference removes most annotations inside function bodies, while explicit public types document package boundaries.
The language deliberately keeps its surface small. It does not add exceptions, implicit conversions, type classes, or general-purpose metaprogramming. Errors that callers are expected to handle use values such as Result, and pattern matching makes those branches visible. The restriction is part of the maintenance model: code should have few hidden control paths and few equivalent forms.
Typed Code over a Dynamically Typed Runtime
BEAM terms do not carry Gleam’s static guarantees. Gleam establishes those guarantees during compilation, then emits code for a runtime whose values remain dynamically represented. This creates a clear trust boundary.
Calls between Gleam modules remain checked. Gleam can also call Erlang, Elixir, and other BEAM languages through external function declarations, but the compiler cannot prove that the foreign implementation matches the declared Gleam type. An incorrect external declaration can therefore reintroduce runtime type errors.
Gleam’s concurrency libraries wrap BEAM processes with typed handles and messages. An actor can accept one declared message type even though the underlying mailbox can technically receive any Erlang term. The wrapper narrows a flexible runtime primitive into an interface the compiler can check.
BEAM process: mailbox accepts runtime terms
Gleam actor: public sender accepts Message
This is the central exchange between the two systems. Gleam gives up some of Erlang’s dynamism and metaprogramming in return for local compile-time guarantees. It keeps BEAM’s lightweight processes, preemptive scheduling, distribution, and supervision rather than reconstructing them inside the language.
Beyond the Erlang Target
Gleam v0.16 added a JavaScript backend in June 2021. The compiler emits readable JavaScript and uses the host’s promise-based concurrency instead of trying to reproduce the BEAM process model in a browser. Cross-target packages can share pure Gleam code, but runtime-specific concurrency and external functions remain target boundaries.
This addition separated Gleam’s language design from its original runtime more clearly:
┌→ Erlang source → BEAM and OTP
Gleam source → types ┤
└→ JavaScript → browser or JS runtime
The Erlang target remains the route to BEAM’s fault-tolerant concurrent systems. The JavaScript target extends the same types and syntax to environments where BEAM is unavailable or unsuitable.
Gleam v1.0 shipped in March 2024. Version one covered the language, compiler, build tool, package manager, formatter, language server, and compiler APIs. More importantly, it established semantic-versioning and backwards-compatibility expectations. The project had moved from an experimental typed language on BEAM to a production-oriented platform with its own integrated toolchain.
One Runtime, Several Language Choices
Erlang made concurrency and recovery properties of the runtime and its standard architecture. BEAM made that model efficient enough to deploy and stable enough to outlive several compiler implementations. OTP made the model repeatable across large systems. Gleam arrived later and moved a different class of failures—incorrect data shapes, missing cases, and unsafe refactors—from runtime behaviour into compiler errors.
The layers remain complementary. BEAM cannot prove that every application message has the intended shape. Gleam cannot make an overloaded mailbox bounded or choose a correct supervision strategy. Static types constrain the program that enters the runtime; process isolation and supervision constrain what happens when the running system still fails.