Erlang vs Elixir: Two Languages, One Virtual Machine
Erlang and Elixir are two distinct programming languages that both compile down to bytecode for the same runtime, the BEAM (Bogdan/Björn's Erlang Abstract Machine). Erlang was created at Ericsson in 1986 by Joe Armstrong, Robert Virding, and Mike Williams to build fault-tolerant telecom switches, and it was open-sourced in 1998. Elixir, created by José Valim and first released in 2012, was designed as a modern, more approachable language on top of the same VM, borrowing Ruby's syntax conventions while compiling to the exact same BEAM bytecode as Erlang. Because both languages target BEAM, they share the same concurrency model, the same OTP design principles, and can call each other's compiled modules directly at runtime.
Cricket analogy: It is like two batsmen with completely different techniques — a classic-era stylist such as Sunil Gavaskar and a modern T20 hitter such as Suryakumar Yadav — walking out onto the exact same pitch, bound by the exact same LBW and run-out rules, no matter how differently they hold the bat.
Shared Foundations: The BEAM VM and OTP
The BEAM VM gives both languages lightweight, isolated processes — not OS threads — that communicate exclusively through asynchronous message passing to a process mailbox, with each process holding its own private heap and garbage collector. The scheduler preemptively switches between millions of these processes based on a reduction count rather than cooperative yielding, so a runaway process cannot starve the others. OTP (Open Telecom Platform) is a set of battle-tested design patterns and libraries — gen_server, gen_statem, supervisor, and application — that both Erlang and Elixir programs use to structure supervision trees and implement the 'let it crash' philosophy, where a supervisor restarts a failed process rather than the program defensively catching every possible error.
Cricket analogy: A supervisor restarting a crashed process is like a captain such as MS Dhoni immediately sending in the next batsman the moment a wicket falls, keeping the innings moving rather than the whole team huddling to dissect exactly what went wrong.
Syntax and Language Design
Erlang's Prolog-Derived Syntax
Erlang's syntax traces back to Prolog, from which it inherited pattern matching, the '=' operator as an assertion rather than assignment, and a clause-based function definition style where multiple function heads are separated by semicolons and terminated by a period. Atoms are lowercase identifiers like ok or error, variables must start with an uppercase letter, and a module begins with a -module() attribute and an explicit -export() list declaring which functions are public. This terse, symbol-heavy syntax — commas between arguments, semicolons between clauses, periods ending a function — is often the biggest hurdle for newcomers, even though the underlying semantics of pattern matching, immutability, and recursion are identical to Elixir's.
Cricket analogy: Erlang's semicolon-separated function clauses are like a bowler such as Jasprit Bumrah having several pre-planned deliveries — a yorker, a bouncer, a slower ball — each one selected precisely to match a specific match situation he faces.
Elixir's Ruby-Inspired Syntax and the Pipe Operator
Elixir replaces Erlang's punctuation-heavy grammar with do/end blocks, defmodule and def keywords, and string interpolation reminiscent of Ruby, which lowers the learning curve for developers coming from mainstream object-oriented or scripting languages. Its signature feature is the pipe operator |>, which takes the result of the expression on its left and inserts it as the first argument of the call on its right, letting a chain of transformations — such as filtering a list, then mapping it, then summing it — read top-to-bottom instead of nesting function calls inside one another. Elixir also adds protocols (a form of polymorphic dispatch) and structs as sugar on top of the same runtime; underneath, the compiler still lowers everything to the same Core Erlang and BEAM instructions.
Cricket analogy: The pipe operator is like a fielding relay in cricket where the ball goes from Ravindra Jadeja at cover to the wicketkeeper who breaks the stumps — each stage's output feeds directly into the next stage without the ball ever being set down.
-module(math_utils).
-export([sum_list/1]).
sum_list(List) ->
sum_list(List, 0).
sum_list([], Acc) ->
Acc;
sum_list([H | T], Acc) ->
sum_list(T, Acc + H).defmodule MathUtils do
def sum_list(list), do: sum_list(list, 0)
def sum_list([], acc), do: acc
def sum_list([head | tail], acc), do: sum_list(tail, acc + head)
end
# Idiomatic Elixir using Enum and the pipe operator
[1, 2, 3, 4, 5]
|> Enum.filter(&(&1 > 1))
|> Enum.sum()Tooling, Ecosystem, and Interoperability
Erlang projects are typically built with rebar3, which compiles code, resolves dependencies from the Hex package repository, and runs tests via EUnit for unit tests or Common Test (ct) for larger integration suites. Elixir projects use Mix as the equivalent build tool, also fetching dependencies from Hex.pm — a package manager originally created for Elixir but now shared by both ecosystems — and testing with ExUnit. Because both compile to BEAM modules, interoperability is direct and cheap: Elixir code calls Erlang libraries by treating the module name as an atom, for example :lists.reverse(list) or :crypto.hash(:sha256, data), while Erlang code calling into Elixir must reference the compiled module's mangled name, such as 'Elixir.MyModule':my_function(Args), because the Elixir compiler prefixes every module with Elixir. before emitting the .beam file.
Cricket analogy: Hex.pm being shared by both build tools is like a franchise such as Chennai Super Kings drafting overseas players from one shared IPL auction pool that every franchise, Erlang-run or Elixir-run, can bid into.
Hex.pm was originally built for the Elixir community, but rebar3 added first-class Hex support, so an Erlang project can depend on a library published from Elixir tooling (and vice versa) without any conversion step — both ecosystems now largely share one package registry.
Elixir module names are not literal Erlang atoms — the compiler prefixes every Elixir module with 'Elixir.' when emitting the .beam file. Calling MyModule.my_function() from Elixir works transparently, but Erlang code must write 'Elixir.MyModule':my_function(Args) with the quoted, prefixed atom, or the call will fail to resolve.
Metaprogramming and Choosing the Right Tool
Elixir has a first-class macro system: defmacro captures its arguments as unevaluated abstract syntax trees, quote turns literal code into that AST representation, and unquote splices values back into it, which is how libraries like ExUnit implement assert or Ecto implements its schema DSL without any special-cased compiler support. Erlang's equivalent mechanism, the parse_transform, operates directly on the compiler's abstract format during compilation; it is powerful but low-level and comparatively poorly documented next to Elixir macros, and it is generally discouraged for everyday application code, though libraries such as lager have used it. In practice, teams pick Erlang when they want the smallest possible dependency footprint, deep telecom or legacy-system interop, or a team already fluent in its terse syntax; they pick Elixir for faster onboarding, a richer standard library (Enum, Stream, protocols), the Phoenix web framework, and a more active general-purpose community — while both remain equally capable at the OTP/concurrency layer since that logic is identical bytecode either way.
Cricket analogy: Elixir's macro-generated assertions are like a coach such as Rahul Dravid designing a specific net-practice drill before the match even starts, so the pattern is baked in ahead of time rather than improvised live during the innings.
- Both languages target the BEAM VM and share OTP, so fault-tolerance and concurrency guarantees are identical underneath.
- Pick syntax based on team background: Erlang's terse Prolog-style clauses versus Elixir's Ruby-style blocks and the |> pipe operator.
- rebar3/EUnit/Common Test (Erlang) and Mix/ExUnit (Elixir) are the native toolchains; Hex.pm now serves both ecosystems.
- Cross-calling is native: Elixir treats Erlang modules as atoms, while Erlang must use the Elixir.-prefixed name to call Elixir modules.
- Elixir macros (defmacro/quote/unquote) are more ergonomic and better documented than Erlang's parse_transform for building DSLs.
- Elixir generally wins on tooling polish, ecosystem size, and frameworks like Phoenix; Erlang wins on minimal footprint and legacy telecom interop.
- Neither language is 'faster' at the OTP layer — the same BEAM bytecode runs either way, so choose based on people and ecosystem, not raw performance.
Practice what you learned
1. What do both Erlang and Elixir compile to before execution?
2. Which build tool is native to the Elixir ecosystem for compiling projects and managing dependencies?
3. What does the Elixir pipe operator |> do?
4. In Erlang's compile-time metaprogramming, what mechanism directly manipulates the compiler's abstract syntax format?
5. When Erlang code needs to call a compiled Elixir module directly, how must it reference that module?
Was this page helpful?
You May Also Like
Erlang Best Practices: Supervision, OTP, and Fault Tolerance
A practical guide to writing production-grade Erlang: let-it-crash supervision design, correct gen_server callback usage, ETS-backed shared state, precise error handling, and OTP release hygiene.
Building a Chat Server in Erlang
A hands-on walkthrough of building a TCP-based multi-user chat server in Erlang, covering gen_tcp sockets, one process per client, a broadcasting room process, disconnect handling with monitors, and OTP supervision.
Erlang Interview Questions
A guided walkthrough of the Erlang concepts interviewers probe most — the actor-model concurrency system, OTP fault tolerance, pattern matching, and distribution — with the reasoning behind each question.
Erlang Quick Reference
A cheat-sheet tour of Erlang's core data types, common built-in functions, the shell/module workflow, OTP gen_server callbacks, and the pattern-matching idioms you'll reach for every day.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics