Erlang (OTP 29)

This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].

1 Assessment

This page assesses Erlang in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.

Core coupling 69/100 · Ecosystem coupling 76/100 · Engineering readiness 72/100

1.1 Core coupling

1.1.1 Type

  • ❌ Dynamic typing: A value carries its type at run time, so a type fault appears when the expression executes (T1)
  • ❌ No declared parameter or return types: -spec is optional metadata, and the language has no generic type parameters (T5)
  • ❌ No sum types: A tagged tuple such as {ok, Value} or {error, Reason} is a convention, and no type checks the union (T4)
  • ❌ Dispatch is checked at run time: Mod:Fun(Args) resolves when called, so a missing function fails there rather than at compile time (T3)
  • ✅ -spec and Dialyzer give optional static analysis: A success-typing pass over the compiled BEAM files detects inconsistent calls
  • ✅ Atoms serve as named constants: An atom such as ok, error, or undefined is a distinct value, so the language needs no null

1.1.2 Construction

  • ✅ All data is immutable: A term cannot be changed after it is built
  • ✅ A pattern in the clause head selects the construction path: The arguments decide which clause runs
  • ✅ Records and maps declare named structure: -record(user, {name, age}) gives compile-time named fields over a tuple
  • ✅ State is a value threaded through recursion: An OTP behaviour receives its state and returns the new state, so no object owns it
  • ✅ A process is the unit of mutable state: A gen_server owns its state and exposes it by message
  • ⚠️ A record is a tuple: Field access is positional at run time, so the record definition must be shared by every module that uses it

1.1.3 Memory

  • ❌ Collection is per-process and non-deterministic: A process collects its own heap on its own schedule (M2)
  • ✅ A private heap confines a pause: A collection affects one process, and there is no global stop-the-world pause
  • ✅ No manual deallocation: The VM reclaims memory, and a program never frees a term
  • ✅ Off-heap binaries are reference counted: A binary above the heap threshold is shared between processes rather than copied
  • ⚠️ A large live set costs more per collection: A process that accumulates state pays a higher collection cost
  • ⚠️ An ETS table lives outside the collector: The table persists until its owner deletes it or dies

1.1.4 Error

  • ❌ Exceptions are unchecked: throw, error, and exit are not declared, so a caller cannot see the failure set from a signature (Er2)
  • ✅ “Let it crash” makes failure a supervision event: A supervisor restarts a failed process, and the normal path carries no recovery code
  • ✅ Links and monitors define propagation: link/1 couples the fates of two processes, and monitor/2 reports a failure without dying
  • ✅ A pattern-match failure raises a defined error: An unmatched value raises badmatch, and an unmatched clause raises function_clause
  • ✅ try/catch/after is available when recovery is local: A catch clause separates throw, error, and exit
  • ⚠️ A restart resets process state: Supervision restores the process, not the data it held, so state must be recoverable

1.1.5 Concurrency

  • ✅ The actor model is the core of the language: Processes share no memory and communicate by message
  • ✅ spawn, link, and monitor are primitives: Starting and observing a process needs no library
  • ✅ The BEAM schedules preemptively by reductions: A process cannot hold a scheduler indefinitely
  • ✅ Messages are immutable values: A message is copied or shared by reference count, never aliased mutably
  • ✅ Distribution is built into the VM: net_kernel and epmd let a process send to a node as if it were local
  • ⚠️ Distribution is designed, not automatic: Node failure, network partition, and message loss must be handled by the application

1.1.6 Platform

  • ✅ The BEAM is portable and open source: The Erlang runtime runs on Linux, macOS, Windows, and BSD
  • ✅ Hot code loading is a runtime feature: code:load_file/1 replaces a module while the system runs
  • ✅ A release bundles the runtime and the application: release_handler and reltool produce a deployable node
  • ✅ Distribution across nodes is a platform feature: Nodes discover each other and pass messages without an external broker
  • ⚠️ The runtime must be present: An Erlang program runs on the VM, so the artefact is not a native binary on its own

1.1.7 Trade-offs (not coupling)

  • The syntax is Prolog-derived: The form is unfamiliar to most developers; this is a learning property, not a coupling
  • Distribution is a design task: The language supplies the transport, and the application decides the failure semantics
  • Weak CPU-bound performance is a domain fit note: The VM is built for I/O concurrency, not for numeric loops
  • Hot upgrade constrains state handling: The running system can change module code, so the application must supply a code_change/3 callback to migrate state
  • Atoms are named constants in place of null: Absence is carried by an atom convention such as undefined
  • Records are tuples by design: The representation is a deliberate choice made for compactness and speed

1.2 Ecosystem coupling

1.2.1 Performance

  • ✅ The BEAM handles very many concurrent I/O processes: A process is cheap and needs no thread
  • ✅ BeamAsm compiles hot code: The JIT added in OTP 24 means bytecode is not always interpreted
  • ✅ Per-process collection avoids a global pause: A collection affects one process
  • ✅ Reduction scheduling bounds latency: A process yields after a fixed number of reductions
  • ⚠️ CPU-bound computation is weak relative to native code: A tight numeric loop trails C or Rust
  • ⚠️ A NIF runs on a scheduler thread: A native function that runs long blocks other processes on that scheduler

1.2.2 Ecosystem

  • ✅ Hex.pm and rebar3 cover packages and builds: A registry, dependency resolution, and release builds are all present
  • ✅ OTP ships with the language: Supervision, gen_server, application, and release handling are part of the distribution
  • ✅ Elixir libraries are reachable through interop: The two languages run on one VM and call each other directly
  • ✅ The flagship projects are long-lived and battle-tested: RabbitMQ, CouchDB, Riak, and WhatsApp’s messaging stack are Erlang systems
  • ✅ OTP is documented as a first-class topic: The OTP design principles document the behaviours
  • ⚠️ The library count is smaller than the mainstream: Fewer general-purpose packages exist outside messaging and telecom

1.2.3 Evolution

  • ✅ The language has kept backward compatibility for decades: Code written against an old OTP release continues to run
  • ✅ Hot code loading decouples deployment from runtime: A module is replaced while the system runs
  • ✅ code_change/3 migrates state across a version change: An OTP behaviour converts old state to the new shape
  • ✅ OTP release handling gives a controlled upgrade path: release_handler applies a version change to a running node
  • ⚠️ A hot upgrade must be designed: State migration and dependency order are the application’s responsibility

1.2.4 Domain

  • ✅ Excellent fit for distributed, fault-tolerant systems: Supervision, distribution, and the actor model match the problem directly
  • ✅ Proven in telecom and messaging: WhatsApp, RabbitMQ, and Discord’s infrastructure run on the BEAM
  • ✅ Binary parsing is a strength: The bit syntax <<A:8, B:16/little>> matches a protocol frame directly
  • ❌ Poor fit for CPU-bound numerical work: The VM is not built for dense numeric computation, so that work belongs in another language (D2)
  • ⚠️ Distribution across nodes requires explicit design effort: The application must decide how to handle a lost node

1.2.5 Learning

  • ❌ The learning curve is steep: A developer learns functional programming, the actor model, and OTP at once (L1)
  • ❌ The Prolog-derived syntax is unfamiliar: The ., ,, and ; delimiters and the term syntax differ from the mainstream (L1)
  • ⚠️ The surface is large: The language, the OTP behaviours, and the VM’s semantics are separate bodies of knowledge (L2)
  • ✅ The official documentation is thorough: The Erlang reference manual and the OTP design principles are authoritative
  • ✅ The community is established: Decades of accumulated material and answered questions exist

1.2.6 Syntax

  • ✅ The term syntax is uniform: Every construct is a term, and a module is a sequence of forms ending in a period
  • ✅ The delimiter roles are consistent: A comma separates expressions, a semicolon separates clauses, and a period ends a form
  • ✅ Bit syntax is compact for protocols: A binary pattern states field widths and endianness in one expression
  • ✅ Pattern matching is part of the function-head syntax: A clause states its cases directly
  • ⚠️ The comma, semicolon, and period distinction is easy to get wrong: One misplaced delimiter changes the meaning of a form
  • ⚠️ Atoms and variables differ only by case: ok is an atom and Ok is a variable

1.2.7 Testing

  • ✅ EUnit and Common Test ship with OTP: Unit tests and system tests are part of the standard distribution
  • ✅ Property-based testing is available: PropEr and QuickCheck generate inputs from a property
  • ✅ Tracing and observer inspect a running system: dbg, recon, and the observer tool expose processes, messages, and scheduler load
  • ✅ Common Test runs a distributed suite: A test can start several nodes and drive them together
  • ❌ Errors surface at run time: Dynamic typing defers a type fault to the executed path (G3)
  • ⚠️ Dialyzer catches some faults and not all: Success typing proves the absence of some inconsistencies, not the presence of correctness

1.2.8 Security

  • ✅ Process isolation contains a fault: A crashed process does not corrupt another process’s memory
  • ✅ Messages are immutable values: No shared mutable state exists to race on
  • ✅ The supervision tree bounds a failure: A restarted process returns the system to a known state
  • ⚠️ The atom table has a fixed limit: list_to_atom/1 or binary_to_atom/2 on untrusted input can exhaust the table, whose default ceiling is about one million atoms
  • ⚠️ binary_to_term/1 on untrusted input is dangerous: Decoding an external term can allocate a large structure unless the safe option is used
  • ⚠️ A NIF and a Port reach unsafe code: Native code can break the VM’s guarantees

1.3 Engineering readiness

1.3.1 Reproducibility

  • ❌ Builds are not byte-reproducible by default.

1.3.2 Supply chain

  • ⚠️ Published packages carry no signed provenance by default.
  • ⚠️ The package registry is a flat name space.

1.3.3 Observability

  • ⚠️ No stable tracing and metrics standard.

1.3.4 Feedback

  • ✅ No charged mechanism in this dimension.

1.3.5 Interop

  • ✅ No charged mechanism in this dimension.

1.3.6 Governance

  • ✅ No charged mechanism in this dimension.

1.4 Profile

Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.

1.4.1 Energy and cost

What this axis asks: what does a running program cost in energy and time?

  • ⚠️ Runs on a virtual machine, above native code in the energy and time benchmarks [5]; a causal re-analysis attributes most of the gap to execution time [6].

1.4.2 AI and agent readiness

What this axis asks: how well does a coding model and an agent work in this language?

  • ❌ Sparse training data: models write it unreliably [7].

1.4.3 Safety certification

What this axis asks: is there a certified or standards-qualified path to a safety-critical system?

  • ⚠️ No certified or standards-qualified path for safety-critical systems.

1.5 Scoring detail

1.5.1 Core coupling Dimensions

Dimension Mechanism Description Present Cost
Type T1 dynamic no static check ✅ −15
T2 weak static — 0
T3 dynamic/duck dispatch ✅ −8
T4 no sum types ✅ −4
T5 no generics ✅ −4
T6 no namespaces — 0
T7 type erasure/reflection — 0
T8 operator overloading — 0
T9 inheritance required for polymorphism — 0
T10 no null-safety — 0
T11 no module boundary — 0
T12 no effect tracking ✅ −8
T13 async/sync colouring — 0
T14 unchecked foreign boundary ✅ −4
Type subtotal −15 (−43)
Construction C1 global mutable state by default — 0
C2 no immutability by default — 0
C3 constructors coupled to allocation — 0
C4 no constructor/validation — 0
Construction subtotal 0
Memory M1 manual deallocation — 0
M2 GC non-deterministic pauses ✅ −8
M3 no RAII — 0
M4 no ownership model — 0
Memory subtotal −8
Error Er1 no structured path — 0
Er2 unchecked exceptions only ✅ −8
Er3 errors ignored/non-exhaustive — 0
Er4 no composable result type — 0
Error subtotal −8
Concurrency K1 GIL/no parallel — 0
K2 shared mutable state default — 0
K3 manual sync no race detection — 0
K4 no structured concurrency — 0
K5 no primitives — 0
Concurrency subtotal 0
Platform P1 cannot function outside one vendor/domain — 0
P2 single-vendor but other uses — 0
P3 legacy/discontinued platform — 0
Platform subtotal 0

Core Dimensions score: 69/100 — total cost = −31 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.2 Ecosystem coupling Dimensions

Dimension Mechanism Description Present Cost
Performance R1 interpreted no AOT/JIT — 0
R2 GC pauses — 0
R3 JIT warmup — 0
Performance subtotal 0
Ecosystem Ec1 no package manager — 0
Ec2 fragmented tooling — 0
Ec3 small/niche/discontinued — 0
Ec4 proprietary — 0
Ec5 platform-locked tooling — 0
Ec6 monoculture — 0
Ec7 legacy burden — 0
Ecosystem subtotal 0
Evolution V1 breaking changes — 0
V2 frozen — 0
V3 discontinued — 0
V4 glacial standards — 0
Evolution subtotal 0
Domain D1 domain/vendor lock-in — 0
D2 poor domain fit ✅ −8
D3 framework forces architecture — 0
Domain subtotal −8
Learning L1 steep ✅ −8
L2 large surface/multi-paradigm ✅ −4
L3 sparse docs — 0
Learning subtotal −12
Syntax S1 verbose — 0
S2 inconsistent — 0
S3 punctuation-heavy — 0
Syntax subtotal 0
Testing G1 weak tooling — 0
G2 poor debugging — 0
G3 runtime-only errors ✅ −4
Testing subtotal −4
Security Q1 memory-unsafe — 0
Q2 dynamic/weak typing risk — 0
Q3 known vulnerability/injection — 0
Security subtotal 0

Meta Dimensions score: 76/100 — total cost = −24 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.3 Engineering readiness Dimensions

Dimension Mechanism Description Present Cost
Reproducibility Y1 build not reproducible ✅ −8
Y2 dependencies not pinned — 0
Reproducibility subtotal −8
Supply chain Y3 no signed provenance ✅ −8
Y4 registry without namespacing ✅ −4
Supply chain subtotal −12
Observability Y5 no stable tracing standard ✅ −8
Y6 no structured logging — 0
Observability subtotal −8
Feedback Y7 slow build — 0
Y8 no fast in-editor analysis — 0
Feedback subtotal 0
Interop Y9 no foreign-function interface — 0
Y10 no portable target — 0
Interop subtotal 0
Governance Y11 single-vendor governance — 0
Y12 closed specification — 0
Governance subtotal 0

Readiness Dimensions score: 72/100 — total cost = −28 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

Verdict: Erlang’s design yields moderate language-imposed coupling in the domain it was built for. The actor model removes shared memory from concurrency, immutability removes in-place mutation, and “let it crash” with OTP supervision moves recovery out of the business logic. Main strengths: processes are cheap and isolated, the BEAM schedules preemptively, per-process collection avoids a global pause, and hot code loading decouples deployment from runtime. Main weaknesses: the language is dynamically typed, so a type fault surfaces at run time; exceptions are unchecked; the syntax is unfamiliar; and distribution across nodes must be designed rather than assumed.


The formal development. The definitions, theorems, and proofs behind this page are published in:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
Ericsson, “Erlang reference manual.” Ericsson, 2024. Available: https://www.erlang.org/doc/
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[7]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.