Side-by-side, interactive cheatsheets for Erlang programmers
comparing Erlang to other languages. Every example runs live in your browser — no
setup, no installation.
Choose your own path by reordering languages
Concurrency, revisited with shared memory. Goroutines and channels are Go's answer to the same problem Erlang solved first with processes and message passing — but goroutines are NOT isolated the way Erlang processes are, which is the biggest thing to unlearn.
go func() is a direct descendant of spawn/1 — but goroutines share memory directly, where Erlang processes share nothing at allreceivevalue, err := f() is {ok, Value}/{error, Reason} spread across two return values instead of one tagged tuplecase, no function clauses, no destructuring; everything goes through if/switch and manual field accesspanic/recover echoes "let it crash," but scoped to one goroutine with a manual defer — there is no OTP-style supervision tree built inThe most extreme contrast on the whole site. The BEAM that runs Erlang is itself written in C, yet the two languages sit at opposite ends of nearly every design axis: automatic per-process GC vs. none at all, isolated share-nothing processes vs. no concurrency primitive built into the language, and a clean catchable crash vs. undefined behavior with no guaranteed outcome at all.
malloc with a free by hand, foreverenum + union, with none of the automatic safety Erlang's pattern match provides#define) are a genuine direct parallel to Erlang's own -define — a rare point of agreement between two otherwise opposite languagesReliability from the opposite direction. Erlang trusts a pattern match at runtime and lets the process crash, relying on isolation and supervision to recover; Rust's ownership and borrow checker reject entire categories of bugs — data races, use-after-free — before the program ever runs. The single biggest new concept: Rust has no garbage collector at all.
Option/Result formalize {ok, Value}/{error, Reason} almost verbatim, and the ? operator propagates failure automatically where Erlang re-pattern-matches by hand at every stepmatch reads close to Erlang function clauses and guards, with compiler-enforced exhaustiveness Erlang cannot offerSend/Sync traits enforce safe sharing at compile time instead of via isolationpanic! echoes "let it crash," but scoped narrowly — idiomatic Rust reaches for Result far more often than idiomatic Erlang avoids a crashA rare shared trait, then a hard syntax turn. Clojure is one of the only other dynamically-typed languages in this whole comparison — the type-system contrast that dominates every other pairing simply does not apply here. What replaces it: Lisp's parenthesized prefix notation, and a concurrency model built on shared atoms/refs instead of Erlang's isolated, message-passing processes.
(+ 2 (* 3 4)) instead of 2 + 3 * 4 — no precedence table to memorize, since nesting parens IS the precedence:ok) play the role Erlang atoms play, and are themselves callable as one-argument map lookups — something no Erlang atom can docond replaces Erlang's case/function clauses; matching Erlang's built-in case requires reaching for the separate core.match library'(+ 2 3) is both syntax and an inspectable list) — something Erlang has no equivalent forTwo roads to reliability, from a common functional starting point. Both languages favor immutability, pattern matching, and recursion — but Erlang trusts a pattern match at runtime and lets the process crash, while Haskell's Hindley-Milner type system rejects entire categories of bugs before the program ever runs.
[X || Generator] was directly inspired by Haskell's [x | generator]when/| guards read almost like a mechanical translation of each other{ok, Value}/{error, Reason} convention is exactly what Maybe/Either formalize — except the compiler now requires handling both cases-spec/Dialyzer is optional and separate, Haskell's checker is mandatory and built inThe closest syntactic relative to Erlang on the whole site. Both languages run top-level script code with no boilerplate main function, both lean on pattern matching as the primary control-flow tool, and F#'s discriminated unions formalize Erlang's tagged-tuple idiom as real, compiler-checked sum types.
match reads almost like a direct translation of Erlang function clauses, guards and allOption/Result formalize {ok, Value}/{error, Reason} — F# even reuses the bare word OkMailboxProcessor ("F# Agents") is explicitly documented as Erlang-style actors for .NET — mailboxes, Post, and Receive echo spawn/!/receive directlyThe strongest actor-model parallel on the whole site. Both languages put actors at the absolute center of the language, not layered on top as a library — but where Erlang guarantees isolation by always copying data across a process boundary, Pony proves sharing is safe at compile time through reference capabilities, letting it pass data between actors with zero copying at all.
actor and be map directly onto Erlang processes and ! — both process exactly one message at a time, in order, with no locks needed for internal stateiso, val, ref, box, tag, trn) have no Erlang equivalent — the compiler proves a reference is safe to share instead of Erlang's always-copy guaranteefun f(): T ?) replace {error, Reason}, but Pony's error carries no payload — no built-in idiom is as convenient as Erlang's tagged tupleThe paradigm shift hiding behind familiar syntax. Prolog looks almost like home — atoms, [Head|Tail] lists, and = as real unification are all things Erlang already does. But Prolog goals can have many solutions where Erlang functions have exactly one, and that single difference reorganizes everything else.
[Head|Tail] list syntax and cons-list semantics are identical, character for character= really is unification in both languages — but Prolog goals can backtrack into multiple solutions where an Erlang function call has exactly one outcomeassertz/retract offer a genuinely mutable database — the one place Prolog quietly breaks its own purity, the way Erlang processes are a deliberate workaround for its own{badmatch, ...}); a failed unification is just a quiet false in Prolog, with backtracking ready to try something elseThe actor model, formalized on the JVM. Akka — the JVM's most widely used actor framework — was explicitly modeled on Erlang/OTP: mailboxes, message passing, and supervision strategies are direct analogues of the processes, receive, and supervisors an Erlang developer already knows.
SupervisorStrategy (restart, resume, stop, escalate) is a near-literal port of OTP's supervisor restart strategies — the vocabulary transfers, not just the conceptOption/Either formalize the {ok, Value}/{error, Reason} convention Erlang programmers already use by hand — except the compiler now requires handling both cases-spec is optional and separate, Scala's checker is mandatory and built infor/yield — same generator-and-filter idea, different keyword placementval mirrors Erlang's single-assignment binding closely, but Scala also allows real mutation via var — an escape hatch Erlang structurally does not haveThe same BEAM, a friendlier surface. Elixir compiles to the very bytecode your Erlang runs on and shares its processes, supervision, and pattern matching — but wraps them in a Ruby-inspired syntax, a real macro system, protocols, structs, and a build tool (Mix) that a modern team actually enjoys.
send/receive, links, monitors, and OTP behaviours are the ones you already know, callable from either language|> and with replace deeply nested calls and case ladders — the readability win Erlang never gotdefmacro and the quoted AST, instead of parse transformsExUnit, and first-class docs are built in — testing and documentation are part of the language culture