ERLANG FROM FIRST PRINCIPLES PROJECTS
Erlang from First Principles: Project-Based Learning
Your Background: Intermediate Elixir developer who understands concepts (actors, message passing, OTP) but needs to master Erlang syntax, tooling, and the raw BEAM foundation.
Core Concept Analysis
Since you know Elixir, you already understand the what and why of:
- The actor model and lightweight processes
- Message passing and mailboxes
- Supervision trees and fault tolerance
- GenServer, GenStateMachine, Supervisor behaviors
- Pattern matching and immutability
What you’re missing in Erlang:
| Concept Area | What Elixir Abstracts Away |
|---|---|
| Syntax | Erlang uses . to end statements, ; between clauses, , between expressions. Variables are CamelCase, atoms are lowercase. |
| Records vs Maps | Elixir uses %Struct{} which compiles to maps. Erlang traditionally used records (compile-time tuples). |
| OTP Behaviors | Elixir’s use GenServer hides the callback structure. Raw Erlang behaviors expose everything. |
| String Handling | Elixir has "binary strings". Erlang has "charlists" (lists of integers) and <<"binaries">>. |
| Tooling | mix is Elixir-specific. Erlang uses rebar3, erlang.mk, or raw erlc. |
| Macros | Elixir macros are elegant. Erlang uses parse transforms (more powerful, more dangerous). |
| Module System | Elixir namespaces with defmodule Foo.Bar. Erlang modules are flat atoms. |
| BEAM Internals | Elixir hides bytecode. Erlang exposes it via beam_lib, -S flags, etc. |
The Learning Path
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 1: SYNTAX & BASICS (Projects 1-3) │
│ Goal: Write Erlang fluently, understand the differences │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 2: CONCURRENCY RAW (Projects 4-6) │
│ Goal: Processes, links, monitors WITHOUT OTP abstractions │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 3: OTP MASTERY (Projects 7-10) │
│ Goal: Implement behaviors from scratch, understand internals │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 4: BEAM INTERNALS (Projects 11-14) │
│ Goal: Understand what the VM actually does │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 5: ADVANCED & INTEGRATION (Projects 15-18) │
│ Goal: Distributed systems, NIFs, releases, metaprogramming │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ CAPSTONE: PRODUCTION SYSTEM │
│ Goal: Build a complete, production-ready Erlang system │
└─────────────────────────────────────────────────────────────────┘
PHASE 1: SYNTAX & BASICS
Project 1: Erlang Calculator REPL
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Prolog, Haskell, Standard ML
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
- Difficulty: Level 1: Beginner (The Tinkerer)
- Knowledge Area: Language Syntax / REPL Design
- Software or Tool: Erlang Shell (erl)
- Main Book: “Learn You Some Erlang for Great Good!” by Fred Hébert
What you’ll build: An interactive calculator that reads expressions like 3 + 4 * 2, parses them respecting operator precedence, and prints results—all in pure Erlang with a custom REPL loop.
Why it teaches Erlang: This forces you to internalize Erlang’s unique syntax: periods ending expressions, semicolons separating clauses, commas between sequential expressions. You’ll use guards, pattern matching on lists, and recursion—all in Erlang’s style.
Core challenges you’ll face:
- Tokenizing input (splitting
"3+4*2"into[{num,3},{op,add},{num,4},{op,mul},{num,2}]) → maps to Erlang string/list handling - Recursive descent parsing with precedence → maps to pattern matching on lists
- REPL loop (
read -> eval -> print -> loop) → maps to tail recursion - Error handling (invalid input shouldn’t crash) → maps to Erlang try/catch
Key Concepts:
- Erlang Syntax Basics: “Learn You Some Erlang” Chapter 2 - Fred Hébert
- Pattern Matching: “Programming Erlang” Chapter 4 - Joe Armstrong
- Recursion: “Learn You Some Erlang” Chapter 5 (Recursion) - Fred Hébert
- Guards: “Programming Erlang” Chapter 4.4 - Joe Armstrong
Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic programming knowledge, have Erlang installed
Real world outcome:
$ erl
1> c(calc).
{ok,calc}
2> calc:start().
calc> 3 + 4 * 2
11
calc> (3 + 4) * 2
14
calc> 10 / 3
3.3333333333333335
calc> quit
bye
ok
Implementation Hints: Your REPL module will look something like:
-module(calc).
-export([start/0]).
start() -> loop().
loop() ->
case io:get_line("calc> ") of
"quit\n" -> io:format("bye~n");
Line ->
Result = eval(parse(tokenize(Line))),
io:format("~p~n", [Result]),
loop()
end.
Note the Erlang conventions:
- Module name matches filename (
calc.erl) - Export list explicitly declares public functions
io:get_line/1returns string with newlineio:format/2uses~pfor pretty-print,~nfor newline- The
.ends the function,;would separate clauses,,separates expressions
Learning milestones:
- REPL loops and prints → You understand basic Erlang I/O
- Tokenizer handles all operators → You understand list processing in Erlang
- Precedence works correctly → You’ve mastered recursive pattern matching
Project 2: INI File Parser & Writer
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: OCaml, Haskell, Rust
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 2. The “Micro-SaaS / Pro Tool” (Solo-Preneur Potential)
- Difficulty: Level 1: Beginner (The Tinkerer)
- Knowledge Area: File I/O / Parsing
- Software or Tool: INI Configuration Files
- Main Book: “Programming Erlang” by Joe Armstrong
What you’ll build: A library that reads INI configuration files into Erlang data structures and writes them back, handling sections, key-value pairs, and comments.
Why it teaches Erlang: INI parsing exercises Erlang’s file I/O, binary/string handling (the <<"binary">> vs "list" distinction), and record or map structures. You’ll also learn rebar3 for project structure.
Core challenges you’ll face:
- File reading (
file:read_file/1returns binary) → maps to binary vs list strings - Line-by-line parsing → maps to binary pattern matching
- Data representation (sections → keys → values) → maps to maps or records
- Writing back to file → maps to iolist construction
Key Concepts:
- Binaries and Strings: “Learn You Some Erlang” Chapter 6 - Fred Hébert
- File I/O: “Programming Erlang” Chapter 13 - Joe Armstrong
- Maps: “Programming Erlang” Chapter 5.3 - Joe Armstrong
- Records: “Learn You Some Erlang” Chapter 7 - Fred Hébert
Difficulty: Beginner Time estimate: Weekend Prerequisites: Completed Project 1
Real world outcome:
$ cat config.ini
[database]
host = localhost
port = 5432
; This is a comment
[cache]
enabled = true
$ erl
1> c(ini).
{ok,ini}
2> {ok, Config} = ini:read("config.ini").
{ok,#{database => #{host => <<"localhost">>, port => <<"5432">>},
cache => #{enabled => <<"true">>}}}
3> NewConfig = maps:put(redis, #{host => <<"127.0.0.1">>}, Config).
4> ini:write("config_new.ini", NewConfig).
ok
Implementation Hints: Binary pattern matching in Erlang is powerful:
parse_line(<<"[", Rest/binary>>) ->
%% Section header: extract name before "]"
...;
parse_line(<<";", _/binary>>) ->
%% Comment line, skip
comment;
parse_line(Line) ->
%% Key = Value line
case binary:split(Line, <<"=">>) of
[Key, Value] -> {kv, trim(Key), trim(Value)};
_ -> error
end.
Erlang binaries use <<...>> syntax, and /binary in patterns means “rest of the binary.”
Learning milestones:
- Read file into binary → You understand Erlang file operations
- Parse all line types correctly → You’ve mastered binary pattern matching
- Round-trip (read then write) preserves data → You understand iolists
Project 3: Markdown to HTML Converter
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Haskell, OCaml, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool” (Solo-Preneur Potential)
- Difficulty: Level 2: Intermediate (The Developer)
- Knowledge Area: Parsing / Text Processing
- Software or Tool: Markdown
- Main Book: “Programming Erlang” by Joe Armstrong
What you’ll build: A Markdown parser that converts a subset of Markdown (headers, bold, italic, links, code blocks, lists) to HTML.
Why it teaches Erlang: This is a significant parsing project that forces you to think in Erlang idioms. You’ll handle nested structures (lists within lists), inline formatting within block elements, and produce well-formed HTML output using iolists.
Core challenges you’ll face:
- Block vs inline parsing (paragraphs contain bold/italic) → maps to two-pass parsing
- Nested lists → maps to recursive data structures
- State tracking (inside code block? inside list?) → maps to accumulator patterns
- HTML escaping (prevent XSS) → maps to binary transformation
Key Concepts:
- Recursive Data Structures: “Learn You Some Erlang” Chapter 5 - Fred Hébert
- Accumulators: “Programming Erlang” Chapter 4.2 - Joe Armstrong
- IOLists: “Learn You Some Erlang” Chapter 6.3 - Fred Hébert
- Parsing Techniques: “Language Implementation Patterns” Chapter 2 - Terence Parr
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Completed Projects 1-2
Real world outcome:
$ cat README.md
# Hello World
This is **bold** and *italic*.
- Item 1
- Item 2
- Nested item
$ erl
1> c(markdown).
{ok,markdown}
2> {ok, Md} = file:read_file("README.md").
3> Html = markdown:to_html(Md).
<<"<h1>Hello World</h1>\n<p>This is <strong>bold</strong> and <em>italic</em>.</p>\n<ul><li>Item 1</li><li>Item 2<ul><li>Nested item</li></ul></li></ul>">>
4> file:write_file("README.html", Html).
ok
Open README.html in a browser to verify correct rendering.
Implementation Hints: Use a two-pass approach:
- Block pass: Split into paragraphs, headers, lists, code blocks
- Inline pass: Within each block, parse bold, italic, links, code spans
For nested lists, track indentation level and use recursion:
parse_list(Lines, Indent) ->
case next_line_indent(Lines) of
MoreIndent when MoreIndent > Indent ->
{Nested, Rest} = parse_list(Lines, MoreIndent),
...;
_ ->
...
end.
Learning milestones:
- Headers and paragraphs work → Basic block parsing done
- Inline formatting works → You understand recursive descent in Erlang
- Nested lists render correctly → You’ve mastered recursive data structures
PHASE 2: CONCURRENCY RAW
Project 4: Chat Server Without OTP
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Go, Rust, Haskell
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool” (Solo-Preneur Potential)
- Difficulty: Level 2: Intermediate (The Developer)
- Knowledge Area: Concurrency / Networking
- Software or Tool: TCP Sockets
- Main Book: “Programming Erlang” by Joe Armstrong
What you’ll build: A multi-user TCP chat server where each client gets their own process, messages broadcast to all users, and crashes don’t bring down the server—all using raw spawn, spawn_link, and message passing without any OTP behaviors.
Why it teaches Erlang: This is why Erlang exists. Building this without OTP shows you exactly what problems OTP solves. You’ll implement your own “supervisor” logic, handle process death, and manage shared state across processes.
Core challenges you’ll face:
- Accepting TCP connections → maps to gen_tcp module
- One process per client → maps to spawn and message passing
- Broadcasting messages → maps to maintaining process registry
- Handling client disconnection → maps to monitors and exit signals
- Server state (list of connected clients) → maps to stateful receive loops
Key Concepts:
- Processes: “Programming Erlang” Chapter 8 - Joe Armstrong
- Message Passing: “Learn You Some Erlang” Chapter 10 - Fred Hébert
- Links and Monitors: “Learn You Some Erlang” Chapter 12 - Fred Hébert
- TCP Sockets: “Programming Erlang” Chapter 14 - Joe Armstrong
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Completed Phase 1
Real world outcome:
# Terminal 1 - Start server
$ erl
1> c(chat_server).
{ok,chat_server}
2> chat_server:start(4000).
Listening on port 4000...
# Terminal 2 - Client 1
$ telnet localhost 4000
Enter name: Alice
Alice joined the chat
Bob: Hello everyone!
# Terminal 3 - Client 2
$ telnet localhost 4000
Enter name: Bob
Bob joined the chat
> Hello everyone!
Alice: Hi Bob!
# Kill Terminal 2 (Ctrl+C)
# Server continues, Terminal 3 sees: "Alice has left"
Implementation Hints: The architecture:
┌─────────────┐
│ Acceptor │ (accepts connections)
└──────┬──────┘
│ spawns
┌──────────────┼──────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Client 1 │ │ Client 2 │ │ Client 3 │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└──────────────┼──────────────┘
▼
┌─────────────┐
│ Room Process│ (maintains client list)
└─────────────┘
Key Erlang pattern - the receive loop with state:
room_loop(Clients) ->
receive
{join, Pid, Name} ->
monitor(process, Pid),
broadcast(Clients, {joined, Name}),
room_loop([{Pid, Name} | Clients]);
{message, FromPid, Text} ->
Name = find_name(FromPid, Clients),
broadcast(Clients, {msg, Name, Text}),
room_loop(Clients);
{'DOWN', _, process, Pid, _} ->
Name = find_name(Pid, Clients),
NewClients = lists:keydelete(Pid, 1, Clients),
broadcast(NewClients, {left, Name}),
room_loop(NewClients)
end.
Learning milestones:
- Single client can connect and send messages → Basic gen_tcp working
- Multiple clients see each other’s messages → Broadcasting works
- Client disconnect doesn’t crash server → You understand monitors
Project 5: Process Pool Manager
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Go, Rust, C
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model (B2B Utility)
- Difficulty: Level 3: Advanced (The Engineer)
- Knowledge Area: Concurrency / Resource Management
- Software or Tool: Worker Pool
- Main Book: “Erlang and OTP in Action” by Logan, Merritt, Carlsson
What you’ll build: A worker pool that maintains N worker processes, distributes tasks to available workers, queues tasks when all workers are busy, and restarts crashed workers—without using poolboy or OTP behaviors.
Why it teaches Erlang: Pool management is exactly what OTP supervisors and gen_server abstract away. Building it raw teaches you why those abstractions exist. You’ll handle the tricky edge cases: what if a worker dies mid-task? What if the pool manager dies?
Core challenges you’ll face:
- Worker lifecycle (spawn, monitor, restart) → maps to process management
- Task distribution (round-robin vs least-loaded) → maps to state management
- Queue management (tasks waiting for workers) → maps to queue data structure
- Handling worker crashes → maps to monitors and restart logic
- Graceful shutdown (wait for tasks to complete) → maps to coordinated termination
Key Concepts:
- Monitors vs Links: “Learn You Some Erlang” Chapter 12 - Fred Hébert
- Process Registry: “Programming Erlang” Chapter 8.4 - Joe Armstrong
- Queue Module: Erlang Documentation -
queuemodule - Restart Strategies: “Erlang and OTP in Action” Chapter 4 - Logan et al.
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Completed Project 4
Real world outcome:
$ erl
1> c(pool).
{ok,pool}
2> Pool = pool:start(3). % 3 workers
<0.85.0>
3> pool:status(Pool).
{workers, 3, idle, 3, busy, 0, queue, 0}
4> % Submit 5 tasks (3 run immediately, 2 queue)
4> [pool:submit(Pool, fun() -> timer:sleep(1000), io:format("done~n") end) || _ <- lists:seq(1,5)].
5> pool:status(Pool).
{workers, 3, idle, 0, busy, 3, queue, 2}
% After 1 second:
done
done
done
6> pool:status(Pool).
{workers, 3, idle, 1, busy, 2, queue, 0}
% After another second:
done
done
7> pool:status(Pool).
{workers, 3, idle, 3, busy, 0, queue, 0}
8> % Test crash handling - worker should restart
8> pool:submit(Pool, fun() -> exit(crash) end).
9> pool:status(Pool). % Still 3 workers!
{workers, 3, idle, 3, busy, 0, queue, 0}
Implementation Hints: Pool manager state structure:
-record(pool_state, {
size :: integer(),
workers :: [pid()], % All worker PIDs
available :: [pid()], % Idle workers
busy :: [{pid(), ref()}], % {Worker, TaskRef}
queue :: queue:queue() % Waiting tasks
}).
Key insight: Use monitors, not links. Links propagate crashes bidirectionally. Monitors just notify you when a process dies:
start_worker() ->
Pid = spawn(fun worker_loop/0),
monitor(process, Pid),
Pid.
When you receive {'DOWN', Ref, process, Pid, Reason}, spawn a replacement and redistribute any queued tasks.
Learning milestones:
- Fixed pool runs tasks → Basic pool works
- Tasks queue when busy → State management correct
- Workers restart after crash → Monitor handling works
- Pool handles backpressure (rejects when queue too long) → Production-ready thinking
Project 6: Rate Limiter with Token Bucket
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Go, Rust, C
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model (B2B Utility)
- Difficulty: Level 2: Intermediate (The Developer)
- Knowledge Area: Algorithms / Concurrency
- Software or Tool: API Rate Limiting
- Main Book: “Programming Erlang” by Joe Armstrong
What you’ll build: A token bucket rate limiter that limits requests to N per second per client, using ETS for shared state and a refill process that adds tokens periodically.
Why it teaches Erlang: This introduces ETS (Erlang Term Storage), the in-memory key-value store that enables shared mutable state in the otherwise immutable Erlang world. You’ll also handle timing with erlang:send_after/3.
Core challenges you’ll face:
- ETS table management (create, read, update atomically) → maps to ETS operations
- Atomic operations (check-and-decrement must be atomic) → maps to ets:update_counter/3
- Token refill timing → maps to erlang:send_after/3
- Multiple buckets (per-client rate limiting) → maps to table design
- Cleanup (remove stale client entries) → maps to garbage collection patterns
Key Concepts:
- ETS Tables: “Learn You Some Erlang” Chapter 14 - Fred Hébert
- ETS Advanced Operations: “Erlang and OTP in Action” Chapter 6 - Logan et al.
- Timers: “Programming Erlang” Chapter 8.7 - Joe Armstrong
- Atomic Updates: Erlang Documentation -
ets:update_counter/3
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Completed Project 4
Real world outcome:
$ erl
1> c(rate_limiter).
{ok,rate_limiter}
2> rate_limiter:start(10, 1000). % 10 requests per 1000ms
ok
3> % Make 10 requests quickly (all should succeed)
3> [rate_limiter:check(client1) || _ <- lists:seq(1,10)].
[ok,ok,ok,ok,ok,ok,ok,ok,ok,ok]
4> % 11th request should be rate limited
4> rate_limiter:check(client1).
{error, rate_limited}
5> % Different client has its own bucket
5> rate_limiter:check(client2).
ok
6> % Wait 1 second, client1 has tokens again
6> timer:sleep(1000).
7> rate_limiter:check(client1).
ok
Implementation Hints: Create ETS table with atomic counter support:
init(MaxTokens, RefillMs) ->
ets:new(?TABLE, [named_table, public, set]),
spawn(fun() -> refill_loop(MaxTokens, RefillMs) end).
The check function uses atomic update:
check(ClientId) ->
case ets:update_counter(?TABLE, ClientId, {2, -1, 0, 0}, {ClientId, MaxTokens}) of
N when N > 0 -> ok;
0 -> {error, rate_limited}
end.
The cryptic {2, -1, 0, 0} means: “Update field 2, subtract 1, but floor at 0, and if already 0 return 0.”
Learning milestones:
- Basic rate limiting works → ETS fundamentals understood
- Tokens refill correctly → Timer handling works
- High concurrency (1000 clients) works → ETS scales
PHASE 3: OTP MASTERY
Project 7: Build Your Own GenServer
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (Erlang-specific)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
- Difficulty: Level 3: Advanced (The Engineer)
- Knowledge Area: OTP Internals / Behavior Design
- Software or Tool: OTP Behaviors
- Main Book: “Erlang and OTP in Action” by Logan, Merritt, Carlsson
What you’ll build: A simplified gen_server behavior from scratch that handles call (synchronous), cast (asynchronous), info (other messages), and proper termination—understanding exactly how the real gen_server works.
Why it teaches Erlang: This demystifies OTP completely. You’ll understand why handle_call returns {reply, Reply, NewState}, why call blocks until response, and how gen_server manages the receive loop.
Core challenges you’ll face:
- Synchronous call/reply (
gen_server:callblocks) → maps to reference-based reply matching - Handling all message types (call, cast, info) → maps to message tagging
- Callback invocation (calling module’s
handle_*functions) → maps to module as parameter - Timeout handling → maps to receive timeouts
- Clean termination → maps to terminate callback
Key Concepts:
- GenServer Internals: “Erlang and OTP in Action” Chapter 3 - Logan et al.
- Behaviors: “Learn You Some Erlang” Chapter 13 - Fred Hébert
- Receive with Timeout: “Programming Erlang” Chapter 8.6 - Joe Armstrong
- References for Reply Matching: “Programming Erlang” Chapter 8.8 - Joe Armstrong
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Completed Phase 2
Real world outcome:
$ erl
1> c(my_gen_server), c(counter_example).
{ok,counter_example}
%% counter_example uses my_gen_server behavior:
2> {ok, Pid} = counter_example:start_link(0).
{ok,<0.85.0>}
3> counter_example:increment(Pid).
ok % This was a cast (async)
4> counter_example:get(Pid).
1 % This was a call (sync)
5> counter_example:increment(Pid).
6> counter_example:increment(Pid).
7> counter_example:get(Pid).
3
8> counter_example:stop(Pid).
ok % Clean termination
Implementation Hints: The core receive loop:
loop(Module, State) ->
receive
{'$call', From, Request} ->
case Module:handle_call(Request, From, State) of
{reply, Reply, NewState} ->
reply(From, Reply),
loop(Module, NewState);
{noreply, NewState} ->
loop(Module, NewState);
{stop, Reason, Reply, NewState} ->
reply(From, Reply),
Module:terminate(Reason, NewState)
end;
{'$cast', Request} ->
case Module:handle_cast(Request, State) of
{noreply, NewState} ->
loop(Module, NewState);
{stop, Reason, NewState} ->
Module:terminate(Reason, NewState)
end;
Info ->
case Module:handle_info(Info, State) of
{noreply, NewState} ->
loop(Module, NewState);
{stop, Reason, NewState} ->
Module:terminate(Reason, NewState)
end
end.
The call function that blocks:
call(Pid, Request) ->
Ref = make_ref(),
Pid ! {'$call', {self(), Ref}, Request},
receive
{Ref, Reply} -> Reply
after 5000 ->
exit(timeout)
end.
reply({Pid, Ref}, Reply) ->
Pid ! {Ref, Reply}.
Learning milestones:
- Call/cast work → You understand the basic pattern
- Timeout works → You understand receive timeouts
- Use your behavior to build a real module → It’s actually usable!
Project 8: Supervision Tree Visualizer
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Elixir (for comparison), Go
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 2. The “Micro-SaaS / Pro Tool” (Solo-Preneur Potential)
- Difficulty: Level 3: Advanced (The Engineer)
- Knowledge Area: OTP / Introspection
- Software or Tool: OTP Supervisor
- Main Book: “Designing for Scalability with Erlang/OTP” by Cesarini & Vinoski
What you’ll build: A tool that introspects a running Erlang application’s supervision tree and outputs an ASCII or Graphviz visualization showing supervisors, workers, their strategies, and current status.
Why it teaches Erlang: You’ll learn the introspection APIs (supervisor:which_children/1, sys:get_status/1, etc.) and deeply understand how supervision trees are structured. This is knowledge that transfers directly to debugging production systems.
Core challenges you’ll face:
- Introspection (getting supervisor children) → maps to supervisor module API
- Recursive tree walking → maps to nested supervisor handling
- Process information (memory, message queue, state) → maps to sys module
- Output formatting (ASCII tree or DOT format) → maps to string building
- Registered names vs PIDs → maps to process registration
Key Concepts:
- Supervisor API: “Erlang and OTP in Action” Chapter 4 - Logan et al.
- sys Module: “Learn You Some Erlang” Chapter 14.3 - Fred Hébert
- Process Info: “Programming Erlang” Chapter 8.9 - Joe Armstrong
- Introspection: “Designing for Scalability” Chapter 5 - Cesarini & Vinoski
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Understanding of OTP supervisors
Real world outcome:
$ erl -pa ebin
1> application:start(my_app).
ok
2> sup_viz:print(my_app_sup).
my_app_sup (supervisor, one_for_one)
├── db_pool_sup (supervisor, one_for_all)
│ ├── db_conn_1 (gen_server) [memory: 45KB, msgs: 0]
│ ├── db_conn_2 (gen_server) [memory: 42KB, msgs: 0]
│ └── db_conn_3 (gen_server) [memory: 43KB, msgs: 2]
├── cache_server (gen_server) [memory: 128KB, msgs: 0]
└── web_acceptor_sup (supervisor, simple_one_for_one)
├── conn_handler_1 (gen_server) [memory: 12KB, msgs: 0]
├── conn_handler_2 (gen_server) [memory: 11KB, msgs: 1]
└── ... (47 more children)
3> sup_viz:to_dot(my_app_sup, "tree.dot").
ok
$ dot -Tpng tree.dot -o tree.png
Implementation Hints: Getting supervisor children:
get_children(SupPid) ->
supervisor:which_children(SupPid).
% Returns: [{Id, Pid, Type, Modules}, ...]
% Type is 'worker' or 'supervisor'
Getting process info:
get_info(Pid) ->
case erlang:process_info(Pid, [memory, message_queue_len, registered_name]) of
undefined -> dead;
Info -> Info
end.
For nested supervisors, recurse:
walk_tree(SupPid, Depth) ->
Children = get_children(SupPid),
lists:map(fun({Id, Pid, Type, _Mods}) ->
case Type of
supervisor ->
{Id, Pid, supervisor, walk_tree(Pid, Depth+1)};
worker ->
{Id, Pid, worker, get_info(Pid)}
end
end, Children).
Learning milestones:
- Single-level supervisor visualized → Basic introspection works
- Nested trees render correctly → Recursive walking works
- Include process health metrics → Deep introspection mastered
Project 9: Distributed Key-Value Store with Mnesia
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (Mnesia is Erlang-specific)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure (Enterprise Scale)
- Difficulty: Level 4: Expert (The Systems Architect)
- Knowledge Area: Distributed Systems / Databases
- Software or Tool: Mnesia
- Main Book: “Erlang and OTP in Action” by Logan, Merritt, Carlsson
What you’ll build: A distributed key-value store using Mnesia that replicates data across multiple Erlang nodes, handles node failures gracefully, and provides both RAM and disk-backed storage.
Why it teaches Erlang: Mnesia is Erlang’s built-in distributed database—something no other language has. Learning Mnesia teaches you Erlang’s unique approach to data persistence, transactions, and distributed systems.
Core challenges you’ll face:
- Schema creation (tables, attributes, indexes) → maps to mnesia:create_table/2
- Node clustering → maps to distributed Erlang nodes
- Replication (RAM vs disc copies) → maps to table types
- Transactions (reading, writing, rollback) → maps to mnesia:transaction/1
- Network partitions → maps to mnesia:set_master_nodes/2
Key Concepts:
- Mnesia Basics: “Erlang and OTP in Action” Chapter 6 - Logan et al.
- Distributed Erlang: “Programming Erlang” Chapter 10 - Joe Armstrong
- Transactions: “Learn You Some Erlang” Chapter 15 - Fred Hébert
- CAP Theorem: “Designing Data-Intensive Applications” Chapter 5 - Martin Kleppmann
Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: Completed Projects 7-8
Real world outcome:
# Terminal 1 - Start node1
$ erl -sname node1
(node1@localhost)1> kvstore:start().
ok
(node1@localhost)2> kvstore:put(mykey, "hello").
ok
# Terminal 2 - Start node2 and join cluster
$ erl -sname node2
(node2@localhost)1> kvstore:join('node1@localhost').
ok
(node2@localhost)2> kvstore:get(mykey).
{ok, "hello"} % Replicated!
# Kill Terminal 1 (Ctrl+C twice)
# Terminal 2 still works:
(node2@localhost)3> kvstore:get(mykey).
{ok, "hello"} % Data persisted!
# Restart Terminal 1:
$ erl -sname node1
(node1@localhost)1> kvstore:rejoin().
ok
(node1@localhost)2> kvstore:get(mykey).
{ok, "hello"} % Data recovered!
Implementation Hints: Schema creation:
create_schema(Nodes) ->
mnesia:create_schema(Nodes),
mnesia:start(),
mnesia:create_table(kv, [
{attributes, [key, value, timestamp]},
{disc_copies, Nodes}, % Persist to disk
{type, set}
]).
Transactions:
put(Key, Value) ->
mnesia:transaction(fun() ->
mnesia:write(#kv{key=Key, value=Value, timestamp=erlang:system_time()})
end).
get(Key) ->
mnesia:transaction(fun() ->
case mnesia:read(kv, Key) of
[#kv{value=V}] -> {ok, V};
[] -> not_found
end
end).
Joining a cluster:
join(ExistingNode) ->
pong = net_adm:ping(ExistingNode),
mnesia:change_config(extra_db_nodes, [ExistingNode]),
mnesia:add_table_copy(kv, node(), disc_copies).
Learning milestones:
- Single-node storage works → Mnesia basics understood
- Two-node replication works → Distributed Mnesia works
- Node failure and recovery handled → Production patterns learned
Project 10: State Machine with gen_statem
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Go, Rust, Haskell
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model (B2B Utility)
- Difficulty: Level 3: Advanced (The Engineer)
- Knowledge Area: State Machines / Protocol Implementation
- Software or Tool: gen_statem
- Main Book: “Designing for Scalability with Erlang/OTP” by Cesarini & Vinoski
What you’ll build: A TCP connection state machine implementing a protocol (like a simplified FTP or SMTP) using gen_statem, handling states like greeting, authenticating, authenticated, transferring, etc.
Why it teaches Erlang: gen_statem is OTP’s formal state machine behavior. It’s more powerful than gen_server for protocol implementations. You’ll learn callback mode (state functions vs handle_event), state enter actions, and timeout handling.
Core challenges you’ll face:
- State definition (which states, what transitions) → maps to state function callbacks
- Event handling (what events trigger transitions) → maps to event types
- State enter actions (actions on entering a state) → maps to enter callbacks
- Timeouts (idle timeout, operation timeout) → maps to gen_statem timeouts
- Integration with gen_tcp → maps to active vs passive sockets
Key Concepts:
- gen_statem: “Designing for Scalability” Chapter 7 - Cesarini & Vinoski
- State Machines: “Learn You Some Erlang” Chapter 13.5 - Fred Hébert
- TCP with OTP: “Programming Erlang” Chapter 14 - Joe Armstrong
- Protocol Design: RFC specifications for chosen protocol
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Completed Projects 7-8
Real world outcome:
# Terminal 1 - Start FTP server
$ erl
1> c(ftp_server).
{ok,ftp_server}
2> ftp_server:start(2121).
Listening on port 2121...
# Terminal 2 - Connect with telnet
$ telnet localhost 2121
220 Welcome to Erlang FTP
USER anonymous
331 Password required
PASS guest
230 Login successful
PWD
257 "/" is current directory
LIST
150 Opening data connection
-rw-r--r-- 1 user user 1024 Jan 01 12:00 file.txt
226 Transfer complete
QUIT
221 Goodbye
Implementation Hints:
gen_statem with state functions (each state is a callback):
-module(ftp_statem).
-behaviour(gen_statem).
-export([callback_mode/0, init/1]).
-export([greeting/3, waiting_password/3, authenticated/3]).
callback_mode() -> [state_functions, state_enter].
init([Socket]) ->
{ok, greeting, #{socket => Socket}}.
greeting(enter, _OldState, Data) ->
send(Data, "220 Welcome to Erlang FTP\r\n"),
{keep_state, Data};
greeting({call, From}, {command, "USER", _User}, Data) ->
{next_state, waiting_password, Data, [{reply, From, ok}]};
greeting({call, From}, {command, _, _}, Data) ->
send(Data, "530 Please login first\r\n"),
{keep_state, Data, [{reply, From, ok}]}.
waiting_password(enter, _OldState, Data) ->
send(Data, "331 Password required\r\n"),
{keep_state, Data};
waiting_password({call, From}, {command, "PASS", _Pass}, Data) ->
{next_state, authenticated, Data, [{reply, From, ok}]}.
authenticated(enter, _OldState, Data) ->
send(Data, "230 Login successful\r\n"),
{keep_state, Data}.
Learning milestones:
- State transitions work → Basic gen_statem understood
- Timeouts work (kick idle clients) → Advanced gen_statem features
- Full protocol implemented → Ready for production protocols
PHASE 4: BEAM INTERNALS
Project 11: BEAM Bytecode Analyzer
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (BEAM-specific)
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
- Difficulty: Level 4: Expert (The Systems Architect)
- Knowledge Area: Compiler Internals / VM Internals
- Software or Tool: BEAM VM
- Main Book: “The BEAM Book” by Erik Stenman
What you’ll build: A tool that reads compiled .beam files, disassembles them, and provides analysis: function size, pattern match complexity, tail-call optimization presence, and call graphs.
Why it teaches Erlang: This opens the hood of the BEAM. You’ll understand what your Erlang code compiles to, why some patterns are faster than others, and how the BEAM executes code.
Core challenges you’ll face:
- BEAM file format (chunks: Code, Atom, StrT, etc.) → maps to beam_lib module
- Instruction decoding → maps to BEAM instruction set
- Call graph construction → maps to graph algorithms
- Identifying optimization opportunities → maps to understanding BEAM optimizations
Key Concepts:
- BEAM Architecture: “The BEAM Book” Chapters 1-3 - Erik Stenman
- beam_lib Module: Erlang Documentation
- BEAM Instructions: A Brief BEAM Primer
- Compiler Internals: “The BEAM Book” Chapter 4 - Erik Stenman
Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: Completed Phase 3
Real world outcome:
$ erlc +debug_info example.erl
$ erl
1> c(beam_analyzer).
{ok,beam_analyzer}
2> beam_analyzer:analyze("example.beam").
Module: example
Exported functions: 5
Total BEAM instructions: 847
Functions by size:
example:process_data/2 - 234 instructions
example:parse_input/1 - 156 instructions
example:main/1 - 98 instructions
...
Tail-call optimized: 12/15 recursive functions
Non-tail recursive (potential stack growth):
- example:build_tree/1 at line 45
Call graph:
main/1 -> parse_input/1 -> validate/1
-> process_data/2 -> transform/1
-> aggregate/2
Pattern match complexity:
example:handle_event/2 - 23 clauses (consider refactoring)
Implementation Hints: Reading BEAM files:
analyze(BeamFile) ->
{ok, {Module, Chunks}} = beam_lib:chunks(BeamFile, [
abstract_code,
atoms,
indexed_imports,
exports
]),
...
For disassembly, compile with debug_info and extract abstract code:
{ok, {_, [{abstract_code, {_, Forms}}]}} =
beam_lib:chunks(BeamFile, [abstract_code]).
%% Forms is the Erlang Abstract Format (AST)
To get actual BEAM instructions, you need to dig deeper:
%% Get the Code chunk (raw bytecode)
{ok, Bin} = file:read_file(BeamFile),
%% Parse the BEAM file format manually, or use erts_debug:df(Module)
Learning milestones:
- Extract basic info from BEAM files → beam_lib understood
- Disassemble to instruction level → BEAM format understood
- Generate useful analysis → You understand BEAM optimization
Project 12: Custom BEAM Tracer
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (BEAM-specific)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 3. The “Service & Support” Model (B2B Utility)
- Difficulty: Level 4: Expert (The Systems Architect)
- Knowledge Area: Debugging / Observability
- Software or Tool: BEAM Tracing
- Main Book: “Erlang Programming” by Cesarini & Thompson
What you’ll build: A tracing framework that wraps Erlang’s tracing capabilities (erlang:trace/3, dbg) into a user-friendly tool for debugging production systems: filter by module, function, process; output to file or live; calculate call timing.
Why it teaches Erlang: BEAM’s tracing is legendary—you can trace anything in a live production system with minimal overhead. Understanding tracing is essential for Erlang debugging mastery.
Core challenges you’ll face:
- Trace flags (calls, messages, timestamps) → maps to erlang:trace/3
- Match specifications (filtering what to trace) → maps to dbg:fun2ms/1
- Trace message handling (receiving and formatting) → maps to trace handler processes
- Performance (not overwhelming the tracer) → maps to rate limiting
- Output formatting (human-readable vs machine-parseable) → maps to log formatting
Key Concepts:
- erlang:trace/3: Erlang Documentation
- Match Specifications: “Erlang Programming” Chapter 17 - Cesarini & Thompson
- dbg Module: “Learn You Some Erlang” Appendix A - Fred Hébert
- Recon Library: recon library documentation
Difficulty: Expert Time estimate: 2 weeks Prerequisites: Completed Project 11
Real world outcome:
$ erl
1> my_tracer:start().
ok
2> % Trace all calls to lists:reverse with timing
2> my_tracer:trace({lists, reverse, '_'}, [calls, timing]).
Tracing calls to lists:reverse/_
3> lists:reverse([1,2,3]).
[TRACE] lists:reverse([1,2,3]) -> [3,2,1] (12 µs)
[3,2,1]
4> % Trace messages to a specific process
4> my_tracer:trace_messages(whereis(my_server)).
Tracing messages to <0.85.0>
5> my_server:call(hello).
[MSG] <0.85.0> <- {call, <0.45.0>, hello}
[MSG] <0.45.0> <- {reply, world}
world
6> % Output to file
6> my_tracer:output_file("trace.log").
7> % ... do operations ...
8> my_tracer:stop().
Wrote 1247 trace events to trace.log
Implementation Hints: Setting up a trace:
trace_calls(MFA, Opts) ->
%% Start tracer process to receive trace messages
TracerPid = spawn(fun() -> trace_handler([]) end),
%% Set up trace on the target
erlang:trace(all, true, [call, {tracer, TracerPid}]),
%% Set trace pattern
erlang:trace_pattern(MFA, [{'_', [], [{return_trace}]}], [local]).
The trace handler receives structured messages:
trace_handler(Acc) ->
receive
{trace, Pid, call, {M, F, Args}} ->
io:format("[CALL] ~p:~p(~p)~n", [M, F, Args]),
trace_handler(Acc);
{trace, Pid, return_from, {M, F, Arity}, RetVal} ->
io:format("[RET] ~p:~p/~p -> ~p~n", [M, F, Arity, RetVal]),
trace_handler(Acc);
stop ->
ok
end.
Learning milestones:
- Basic call tracing works → erlang:trace understood
- Match specs filter correctly → Advanced tracing works
- Minimal overhead on traced system → Production-ready
Project 13: Process Heap Inspector
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (BEAM-specific)
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
- Difficulty: Level 4: Expert (The Systems Architect)
- Knowledge Area: Memory Management / VM Internals
- Software or Tool: BEAM Memory Model
- Main Book: “The BEAM Book” by Erik Stenman
What you’ll build: A tool that inspects the internal heap structure of Erlang processes, showing term sizes, binary references, garbage collection stats, and identifying memory leaks (large mailboxes, binary accumulation).
Why it teaches Erlang: Understanding BEAM’s per-process GC and memory model is crucial for production Erlang. This project teaches you to diagnose memory issues that are invisible at the application level.
Core challenges you’ll face:
- Process info extraction → maps to erlang:process_info/2
- Term size calculation → maps to erts_debug:size/1
- Binary reference tracking → maps to reference-counted binaries
- Heap fragmentation analysis → maps to GC internals
- Visualization → maps to report formatting
Key Concepts:
- BEAM Memory Model: “The BEAM Book” Chapter 5 - Erik Stenman
- Process Info: Erlang Documentation -
erlang:process_info/2 - Binary Handling: “Erlang Efficiency Guide” - erlang.org
- GC Internals: “The BEAM Book” Chapter 6 - Erik Stenman
Difficulty: Expert Time estimate: 2 weeks Prerequisites: Completed Project 11
Real world outcome:
$ erl
1> {ok, Pid} = leaky_server:start().
{ok,<0.85.0>}
2> % Let it run for a while...
2> timer:sleep(10000).
3> heap_inspector:analyze(Pid).
Process: <0.85.0> (leaky_server)
======================================
Heap size: 45.2 MB (WARNING: Large heap)
Stack size: 1.2 KB
Message queue: 0 messages
Memory breakdown:
Lists: 2.1 MB (12,847 elements total)
Tuples: 1.4 MB (8,234 tuples)
Binaries: 41.5 MB (WARNING: Binary accumulation detected)
- Ref-counted binaries: 41.3 MB across 847 refs
- Heap binaries: 0.2 MB
Large terms detected:
- List at depth 3: 1.8 MB
- Binary reference: 5.2 MB (held since 847 GC cycles)
GC stats:
Minor GCs: 1247
Major GCs (fullsweep): 3
Fullsweep after: 65535 (PROBLEM: too high, binaries not freed)
DIAGNOSIS: This process is accumulating binary references.
Possible causes:
- Sub-binary references keeping large binaries alive
- Missing explicit binary GC
Recommendation: Lower fullsweep_after to 100 or call
erlang:garbage_collect(Pid) explicitly.
Implementation Hints: Key process_info fields:
analyze(Pid) ->
Info = erlang:process_info(Pid, [
heap_size,
stack_size,
message_queue_len,
garbage_collection,
binary,
current_function,
dictionary
]),
%% binary field shows all ref-counted binaries the process holds
{binary, Binaries} = lists:keyfind(binary, 1, Info),
%% Each is {BinaryId, Size, RefCount}
TotalBinaryMem = lists:sum([Size || {_Id, Size, _Refs} <- Binaries]),
...
Getting term sizes:
%% This requires erlang to be compiled with debug info
term_size(Term) ->
erts_debug:flat_size(Term). % Words used
Learning milestones:
- Extract basic heap stats → process_info understood
- Identify binary accumulation → Common leak pattern detected
- Provide actionable recommendations → Production debugging mastery
Project 14: Scheduler Utilization Monitor
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (BEAM-specific)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 3. The “Service & Support” Model (B2B Utility)
- Difficulty: Level 4: Expert (The Systems Architect)
- Knowledge Area: VM Internals / Performance
- Software or Tool: BEAM Schedulers
- Main Book: “The BEAM Book” by Erik Stenman
What you’ll build: A real-time dashboard showing BEAM scheduler utilization, run queue lengths, reduction counts, and identifying processes that are hogging schedulers or causing imbalanced load.
Why it teaches Erlang: BEAM’s scheduler is what makes Erlang magic—preemptive scheduling via reductions, work-stealing across cores, dirty schedulers for NIFs. Understanding scheduler behavior is key to performance.
Core challenges you’ll face:
- Scheduler stats → maps to erlang:statistics/1
- Run queue monitoring → maps to scheduler run queues
- Reduction tracking → maps to understanding reductions
- Load balancing detection → maps to work stealing
- Real-time updates → maps to periodic sampling
Key Concepts:
- Schedulers: “The BEAM Book” Chapter 7 - Erik Stenman
- Statistics: Erlang Documentation -
erlang:statistics/1 - Reductions: “Erlang Efficiency Guide” - erlang.org
- Performance Analysis: “Erlang Programming” Chapter 19 - Cesarini & Thompson
Difficulty: Expert Time estimate: 1-2 weeks Prerequisites: Completed Project 12
Real world outcome:
$ erl +S 8 +SDcpu 8
1> scheduler_monitor:start().
╔════════════════════════════════════════════════════════════════╗
║ BEAM Scheduler Monitor (8 schedulers) ║
╠════════════════════════════════════════════════════════════════╣
║ Scheduler │ Utilization │ Run Queue │ Reductions/s │ Status ║
╠═══════════╪═════════════╪═══════════╪══════════════╪═══════════╣
║ 1 │ ████████ 87%│ 3 │ 1,234,567 │ HOT ║
║ 2 │ ███░░░░░ 35%│ 0 │ 456,789 │ normal ║
║ 3 │ ██░░░░░░ 22%│ 0 │ 234,567 │ normal ║
║ 4 │ ██░░░░░░ 19%│ 0 │ 198,765 │ normal ║
║ 5 │ █░░░░░░░ 12%│ 0 │ 98,765 │ idle ║
║ 6 │ █░░░░░░░ 11%│ 0 │ 87,654 │ idle ║
║ 7 │ █░░░░░░░ 10%│ 0 │ 76,543 │ idle ║
║ 8 │ █░░░░░░░ 9%│ 0 │ 65,432 │ idle ║
╠════════════════════════════════════════════════════════════════╣
║ WARNING: Scheduler 1 overloaded! Top processes: ║
║ <0.85.0> (worker) - 45% of scheduler 1 reductions ║
║ <0.92.0> (parser) - 23% of scheduler 1 reductions ║
║ Consider: Process migration or parallelization ║
╚════════════════════════════════════════════════════════════════╝
Implementation Hints: Getting scheduler stats:
get_scheduler_utilization() ->
%% Must call wall_clock before and after to calculate utilization
erlang:statistics(scheduler_wall_time),
timer:sleep(1000),
SchedTimes = erlang:statistics(scheduler_wall_time),
%% Returns: [{SchedId, ActiveTime, TotalTime}, ...]
[{Id, Active/Total * 100} || {Id, Active, Total} <- SchedTimes].
Run queue lengths:
get_run_queues() ->
erlang:statistics(run_queue_lengths).
%% Returns: [Q1, Q2, Q3, ...] for each scheduler
Finding reduction hogs:
top_processes(N) ->
Procs = erlang:processes(),
WithReds = [{P, element(2, erlang:process_info(P, reductions))} || P <- Procs],
lists:sublist(lists:reverse(lists:keysort(2, WithReds)), N).
Learning milestones:
- Show scheduler utilization → Basic stats understood
- Detect overloaded schedulers → Load analysis works
- Correlate with specific processes → Full observability achieved
PHASE 5: ADVANCED & INTEGRATION
Project 15: Erlang NIF for Fast JSON
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang + C
- Alternative Programming Languages: Erlang + Rust, Erlang + Zig
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 3. The “Service & Support” Model (B2B Utility)
- Difficulty: Level 4: Expert (The Systems Architect)
- Knowledge Area: FFI / Native Code
- Software or Tool: NIFs (Native Implemented Functions)
- Main Book: “The BEAM Book” by Erik Stenman
What you’ll build: A NIF (Native Implemented Function) that wraps a fast C JSON parser (like yyjson or simdjson) to provide high-performance JSON parsing for Erlang, learning the NIF API along the way.
Why it teaches Erlang: NIFs are how Erlang integrates with native code. You’ll learn the NIF API, resource objects, dirty schedulers (for long-running NIFs), and the tradeoffs of native code in BEAM.
Core challenges you’ll face:
- NIF boilerplate (module setup, function registration) → maps to NIF API
- Type conversion (Erlang terms ↔ C types) → maps to enif_ functions*
- Memory management (who owns what) → maps to resource objects
- Scheduler safety (don’t block schedulers) → maps to dirty NIFs
- Error handling (C errors → Erlang exceptions) → maps to enif_raise_exception
Key Concepts:
- NIF Tutorial: Erlang Documentation - “How to write a NIF”
- NIF API: “The BEAM Book” Chapter 11 - Erik Stenman
- Dirty NIFs: Erlang Documentation - “Dirty NIFs”
- Resource Objects: “The BEAM Book” Chapter 11.4 - Erik Stenman
Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: C programming, completed Phase 3
Real world outcome:
$ make
Compiling json_nif.c...
Compiling Erlang wrapper...
Done.
$ erl -pa ebin
1> json_nif:parse(<<"{\"name\": \"Erlang\", \"version\": 26}">>).
{ok, #{<<"name">> => <<"Erlang">>, <<"version">> => 26}}
2> % Benchmark against pure Erlang JSON
2> big_json = file:read_file("big.json").
3> timer:tc(fun() -> json_nif:parse(big_json) end).
{1234, {ok, ...}} % 1.2 ms with NIF
4> timer:tc(fun() -> jsx:decode(big_json) end).
{15678, ...} % 15.6 ms with pure Erlang
5> % NIF is 12x faster!
Implementation Hints: NIF skeleton in C:
#include "erl_nif.h"
#include "yyjson.h"
static ERL_NIF_TERM parse_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
ErlNifBinary json_bin;
if (!enif_inspect_binary(env, argv[0], &json_bin)) {
return enif_make_badarg(env);
}
yyjson_doc *doc = yyjson_read((char*)json_bin.data, json_bin.size, 0);
if (!doc) {
return enif_make_tuple2(env,
enif_make_atom(env, "error"),
enif_make_atom(env, "parse_error"));
}
ERL_NIF_TERM result = convert_to_term(env, yyjson_doc_get_root(doc));
yyjson_doc_free(doc);
return enif_make_tuple2(env, enif_make_atom(env, "ok"), result);
}
static ErlNifFunc nif_funcs[] = {
{"parse", 1, parse_nif, ERL_NIF_DIRTY_JOB_CPU_BOUND}
};
ERL_NIF_INIT(json_nif, nif_funcs, NULL, NULL, NULL, NULL)
Note ERL_NIF_DIRTY_JOB_CPU_BOUND - this runs on a dirty scheduler so it doesn’t block regular schedulers.
Learning milestones:
- Basic NIF compiles and loads → NIF infrastructure understood
- Simple types convert correctly → Type conversion works
- Complex JSON parses correctly → Full implementation works
- Benchmarks show significant speedup → NIF was worth it
Project 16: Hot Code Upgrade System
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (Erlang-specific)
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 4. The “Open Core” Infrastructure (Enterprise Scale)
- Difficulty: Level 5: Master (The First-Principles Wizard)
- Knowledge Area: Release Engineering / VM Internals
- Software or Tool: OTP Releases
- Main Book: “Erlang and OTP in Action” by Logan, Merritt, Carlsson
What you’ll build: A complete hot code upgrade system for an Erlang application: release packaging, appup files, state transformation during upgrade, and rollback capability—upgrading a running chat server without dropping connections.
Why it teaches Erlang: Hot code loading is Erlang’s killer feature. This project teaches you the full lifecycle: code_change callbacks, appup/relup files, release handling, and the legendary “upgrade without downtime.”
Core challenges you’ll face:
- Release packaging → maps to rebar3 releases
- Appup files (upgrade instructions) → maps to OTP upgrade mechanism
- code_change callback (state transformation) → maps to GenServer upgrades
- Relup generation → maps to systools
- Rollback → maps to release_handler
Key Concepts:
- Releases: “Erlang and OTP in Action” Chapter 10 - Logan et al.
- Appup Files: Erlang Documentation - “Creating .appup Files”
- code_change: “Designing for Scalability” Chapter 10 - Cesarini & Vinoski
- Release Handler: Erlang Documentation - “release_handler”
Difficulty: Master Time estimate: 3-4 weeks Prerequisites: Completed Phase 4
Real world outcome:
# Terminal 1 - Running chat server v1.0.0
$ _build/default/rel/chat/bin/chat foreground
Chat server v1.0.0 starting...
[INFO] Client alice connected
[INFO] Client bob connected
# Terminal 2 - Client alice
$ telnet localhost 4000
Connected to Chat v1.0.0
> Hello from v1!
# Terminal 3 - Deploy upgrade to v1.1.0 (adds timestamps)
$ rebar3 release
$ rebar3 appup generate
$ rebar3 relup
$ _build/default/rel/chat/bin/chat upgrade "1.1.0"
Release upgraded from 1.0.0 to 1.1.0
# Terminal 1 now shows:
[INFO] Upgraded to v1.1.0
[INFO] State migrated for 2 active connections
# Terminal 2 - alice still connected!
> Hello from v1.1!
[12:34:56] alice: Hello from v1.1! <- New timestamp feature!
# Rollback if needed
$ _build/default/rel/chat/bin/chat downgrade "1.0.0"
Implementation Hints:
The code_change callback transforms state:
%% In chat_server.erl (gen_server)
code_change({down, "1.0.0"}, State, _Extra) ->
%% Downgrading from 1.1.0 to 1.0.0
%% Remove timestamp from state
NewState = maps:remove(timestamp_enabled, State),
{ok, NewState};
code_change("1.0.0", State, _Extra) ->
%% Upgrading from 1.0.0 to 1.1.0
%% Add timestamp feature
NewState = State#{timestamp_enabled => true},
{ok, NewState}.
The appup file (chat.appup):
{"1.1.0",
[{"1.0.0", [
{update, chat_server, {advanced, []}}
]}],
[{"1.0.0", [
{update, chat_server, {advanced, []}}
]}]
}.
Learning milestones:
- Release packages correctly → Release basics understood
- Simple upgrade works → Appup understood
- State transforms correctly → code_change works
- Connections survive upgrade → Zero-downtime achieved!
Project 17: Parse Transform Macro System
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (Erlang-specific)
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
- Difficulty: Level 5: Master (The First-Principles Wizard)
- Knowledge Area: Metaprogramming / Compiler
- Software or Tool: Parse Transforms
- Main Book: “Metaprogramming Elixir” by Chris McCord (for concepts, then apply to Erlang)
What you’ll build: A parse transform that adds Elixir-like pipe operator (|>) to Erlang, transforming X |> foo() |> bar(Y) into bar(foo(X), Y) at compile time.
Why it teaches Erlang: Parse transforms are Erlang’s metaprogramming system—more powerful than Elixir macros but also more dangerous. This teaches you the Erlang Abstract Format (AST) and compile-time code transformation.
Core challenges you’ll face:
- Erlang Abstract Format (the AST) → maps to understanding term structure
- Parse transform interface → maps to parse_transform/2 function
- AST walking (find all
|>operators) → maps to recursive tree traversal - AST rewriting (transform to function calls) → maps to term construction
- Error reporting (good compile errors) → maps to compiler integration
Key Concepts:
- Parse Transforms: Erlang Documentation - “Parse Transformations”
- Erlang Abstract Format: Erlang Documentation - “The Abstract Format”
- AST Manipulation: “Language Implementation Patterns” Chapter 5 - Terence Parr
- Compile Module: Erlang Documentation - “compile”
Difficulty: Master Time estimate: 2-3 weeks Prerequisites: Completed Phase 4
Real world outcome:
%% In my_module.erl
-module(my_module).
-compile({parse_transform, pipe_transform}).
-export([example/0]).
example() ->
"hello world"
|> string:uppercase()
|> string:split(" ")
|> lists:reverse()
|> lists:join("-").
%% Compiles to:
%% lists:join("-", lists:reverse(string:split(string:uppercase("hello world"), " "))).
$ erlc pipe_transform.erl
$ erlc my_module.erl
$ erl
1> my_module:example().
"WORLD-HELLO"
Implementation Hints: Parse transform skeleton:
-module(pipe_transform).
-export([parse_transform/2]).
parse_transform(Forms, _Options) ->
[transform_form(Form) || Form <- Forms].
transform_form({function, Line, Name, Arity, Clauses}) ->
{function, Line, Name, Arity, [transform_clause(C) || C <- Clauses]};
transform_form(Other) ->
Other.
transform_clause({clause, Line, Patterns, Guards, Body}) ->
{clause, Line, Patterns, Guards, [transform_expr(E) || E <- Body]}.
%% Find the pipe operator
transform_expr({op, Line, '|>', Left, Right}) ->
%% Transform: Left |> Right into Right(Left) or Right(Left, Args...)
insert_as_first_arg(transform_expr(Left), transform_expr(Right));
transform_expr({call, Line, Fun, Args}) ->
{call, Line, Fun, [transform_expr(A) || A <- Args]};
transform_expr(Other) ->
Other.
insert_as_first_arg(Arg, {call, Line, Fun, Args}) ->
{call, Line, Fun, [Arg | Args]}.
The tricky part is handling the AST format correctly. Use erl_syntax module for easier manipulation:
%% Alternatively, use erl_syntax for cleaner code
transform_expr(Expr) ->
erl_syntax_lib:map(fun transform_node/1, Expr).
Learning milestones:
- Parse transform compiles → Basic structure works
- Simple pipes transform → AST rewriting works
- Nested pipes work → Recursive transformation correct
- Good error messages on invalid input → Production quality
Project 18: Distributed Erlang Game Server
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: Elixir, Go
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 4. The “Open Core” Infrastructure (Enterprise Scale)
- Difficulty: Level 4: Expert (The Systems Architect)
- Knowledge Area: Distributed Systems / Game Development
- Software or Tool: Distributed Erlang
- Main Book: “Designing for Scalability with Erlang/OTP” by Cesarini & Vinoski
What you’ll build: A multiplayer game server (simple real-time game like tag or capture-the-flag) running across multiple Erlang nodes, with automatic player handoff when servers fail, using global and pg for process discovery.
Why it teaches Erlang: Games are the ultimate test of Erlang’s strengths: massive concurrency (player per process), real-time requirements, fault tolerance (server crash shouldn’t end the game), and distribution.
Core challenges you’ll face:
- Player process per connection → maps to massive concurrency
- Game world state (positions, collisions) → maps to shared state patterns
- Node clustering → maps to distributed Erlang
- Process migration (when node fails) → maps to process handoff
- Real-time updates (60 updates/second) → maps to timer:send_interval
- Client protocol (WebSocket for browser) → maps to cowboy websocket
Key Concepts:
- Distributed Erlang: “Programming Erlang” Chapter 10 - Joe Armstrong
- Global Registration: Erlang Documentation - “global”
- Process Groups: Erlang Documentation - “pg”
- Cowboy Websockets: Cowboy documentation
- Game Loops: Game programming resources
Difficulty: Expert Time estimate: 4-6 weeks Prerequisites: Completed all previous phases
Real world outcome:
# Start 3 game server nodes
$ ./start_node.sh node1 8080
$ ./start_node.sh node2 8081
$ ./start_node.sh node3 8082
# Open browser to http://localhost:8080/game
# See: "Connected to node1. 15 players online."
# Move your character with arrow keys
# See other players moving in real-time
# Kill node1 (Ctrl+C)
# Browser automatically reconnects to node2
# Game continues seamlessly!
# "Reconnected to node2. 15 players online."
# Start node1 again
# Some players automatically migrate back to balance load
Implementation Hints: Architecture:
┌─────────────────────────────────────────┐
│ Load Balancer │
└──────────────┬──────────────────────────┘
│
┌─────────────────────────┼─────────────────────────┐
│ │ │
┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐
│ Node1 │◄────────────►│ Node2 │◄────────────►│ Node3 │
│(Erlang) │ Distributed │ (Erlang) │ Erlang │ (Erlang) │
└────┬────┘ Erlang └─────┬────┘ └─────┬────┘
│ │ │
┌─────┼─────┐ ┌─────┼─────┐ ┌─────┼─────┐
│Player│Game│ │Player│Game│ │Player│Game│
│Procs │Loop│ │Procs │Loop│ │Procs │Loop│
└──────┴────┘ └──────┴────┘ └──────┴────┘
Key pattern - player as process with mailbox:
player_loop(State = #{socket := Socket, position := Pos}) ->
receive
{move, Direction} ->
NewPos = calculate_new_position(Pos, Direction),
broadcast_position(State#{position := NewPos}),
player_loop(State#{position := NewPos});
{world_update, WorldState} ->
send_to_client(Socket, encode_world(WorldState)),
player_loop(State);
{node_shutdown, NewNode} ->
%% Migrate to new node
migrate_to(NewNode, State)
after 16 -> % ~60 FPS
player_loop(State)
end.
Using pg for process groups:
%% All players in a game room
pg:join(game_room_1, self()),
%% Broadcast to all players in room
[Pid ! {world_update, State} || Pid <- pg:get_members(game_room_1)].
Learning milestones:
- Single-node game works → Basic architecture correct
- Multi-node clustering works → Distribution works
- Player survives node failure → Fault tolerance achieved
- Smooth gameplay at 60 FPS → Performance tuned
CAPSTONE PROJECT
Project 19: Production-Ready Message Queue (Mini-RabbitMQ)
- File: ERLANG_FROM_FIRST_PRINCIPLES_PROJECTS.md
- Main Programming Language: Erlang
- Alternative Programming Languages: None (this should be pure Erlang)
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 4. The “Open Core” Infrastructure (Enterprise Scale)
- Difficulty: Level 5: Master (The First-Principles Wizard)
- Knowledge Area: Distributed Systems / Message Queues
- Software or Tool: RabbitMQ (as inspiration)
- Main Book: “RabbitMQ in Action” by Videla & Williams (for concepts)
What you’ll build: A simplified but production-quality message queue system with: exchanges, queues, bindings, persistent messages (using Mnesia), consumer acknowledgments, clustering, and an admin API.
Why this is the capstone: This project integrates everything: OTP behaviors (gen_server, supervisor, gen_statem), Mnesia for persistence, distributed Erlang for clustering, NIFs potentially for performance, hot code loading for upgrades, and tracing for debugging. This is why Erlang was invented.
Core challenges you’ll face:
- Exchange/Queue/Binding model → maps to domain modeling
- Message persistence → maps to Mnesia transactions
- Consumer management → maps to process monitoring
- Acknowledgment tracking → maps to state machines
- Clustering → maps to distributed Erlang + Mnesia
- Backpressure → maps to flow control
- Admin API → maps to Cowboy REST
- Metrics → maps to counters + observers
Key Concepts:
- AMQP Model: “RabbitMQ in Action” Chapter 2 - Videla & Williams
- Persistence: “Erlang and OTP in Action” Chapter 6 - Logan et al.
- Flow Control: “Designing for Scalability” Chapter 12 - Cesarini & Vinoski
- Distributed Mnesia: Erlang Documentation - “Mnesia User’s Guide”
Difficulty: Master Time estimate: 2-3 months Prerequisites: Completed all 18 projects above
Real world outcome:
# Start a 3-node cluster
$ ./emq start node1 5672
$ ./emq join node2 5673 node1
$ ./emq join node3 5674 node1
# CLI management
$ ./emq-admin status
Cluster: 3 nodes
node1@localhost: running, 12 queues, 45MB memory
node2@localhost: running, 8 queues, 32MB memory
node3@localhost: running, 10 queues, 38MB memory
$ ./emq-admin declare-exchange events topic
$ ./emq-admin declare-queue user-events --durable
$ ./emq-admin bind user-events events "user.*"
# Producer (in Erlang shell)
1> {ok, Conn} = emq_client:connect("localhost", 5672).
2> emq_client:publish(Conn, "events", "user.created", <<"User 123 created">>).
ok
# Consumer (separate shell)
1> {ok, Conn} = emq_client:connect("localhost", 5672).
2> emq_client:subscribe(Conn, "user-events", fun(Msg) ->
io:format("Got: ~p~n", [Msg]),
ack
end).
Got: {message, <<"user.created">>, <<"User 123 created">>}
# Kill node2
$ ./emq stop node2
# Messages automatically route through remaining nodes
# Queues on node2 failover to node1 and node3
Implementation Hints: This project should use all the patterns you’ve learned:
Supervision tree:
┌──────────────────┐
│ emq_sup │
│ (top supervisor) │
└────────┬─────────┘
┌────────────────────┬┴────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌───────────────┐
│ exchange_sup │ │ queue_sup │ │ connection_sup│
│(one_for_one) │ │(simple_1_1) │ │ (simple_1_1) │
└──────────────┘ └──────────────┘ └───────────────┘
Message flow (as gen_statem):
PUBLISH ROUTE DELIVER
┌───────────┐ ┌──────────────┐ ┌────────────┐
│ Exchange │ ────────►│ Queue │ ────────►│ Consumer │
│ (gen_srv) │ bindings │ (gen_statem) │ msgs │ (gen_srv) │
└───────────┘ └──────────────┘ └────────────┘
│
▼ persist
┌──────────┐
│ Mnesia │
└──────────┘
Queue states (gen_statem):
-type state() :: empty | has_messages | has_consumers | ready.
empty(enter, _, Data) -> {keep_state, Data};
empty({call, From}, {publish, Msg}, Data) ->
persist(Msg, Data),
{next_state, has_messages, add_msg(Msg, Data), [{reply, From, ok}]}.
has_messages(enter, _, Data) ->
%% Try to deliver to consumers
try_deliver(Data);
has_messages({call, From}, {consume, Pid}, Data) ->
{next_state, ready, add_consumer(Pid, Data), [{reply, From, ok}]}.
Learning milestones:
- Single-node pub/sub works → Basic architecture done
- Persistence survives restart → Mnesia integration works
- Clustering works → Distributed Erlang mastered
- Handles 10K messages/second → Performance acceptable
- Node failure handled gracefully → Production-ready
Project Comparison Table
| Project | Difficulty | Time | Depth of Understanding | Fun Factor |
|---|---|---|---|---|
| 1. Calculator REPL | Beginner | Weekend | ★★☆☆☆ | ★★☆☆☆ |
| 2. INI Parser | Beginner | Weekend | ★★☆☆☆ | ★★☆☆☆ |
| 3. Markdown→HTML | Intermediate | 1-2 weeks | ★★★☆☆ | ★★★☆☆ |
| 4. Chat Server (no OTP) | Intermediate | 1-2 weeks | ★★★★☆ | ★★★★☆ |
| 5. Process Pool | Advanced | 1-2 weeks | ★★★★☆ | ★★★☆☆ |
| 6. Rate Limiter | Intermediate | 1 week | ★★★☆☆ | ★★★☆☆ |
| 7. Build GenServer | Advanced | 1-2 weeks | ★★★★★ | ★★★★☆ |
| 8. Supervision Visualizer | Advanced | 1-2 weeks | ★★★★☆ | ★★★☆☆ |
| 9. Mnesia KV Store | Expert | 2-3 weeks | ★★★★★ | ★★★★☆ |
| 10. gen_statem Protocol | Advanced | 2 weeks | ★★★★☆ | ★★★☆☆ |
| 11. BEAM Analyzer | Expert | 2-3 weeks | ★★★★★ | ★★★★★ |
| 12. Custom Tracer | Expert | 2 weeks | ★★★★★ | ★★★★☆ |
| 13. Heap Inspector | Expert | 2 weeks | ★★★★★ | ★★★★☆ |
| 14. Scheduler Monitor | Expert | 1-2 weeks | ★★★★★ | ★★★★☆ |
| 15. JSON NIF | Expert | 2-3 weeks | ★★★★☆ | ★★★★☆ |
| 16. Hot Code Upgrade | Master | 3-4 weeks | ★★★★★ | ★★★★★ |
| 17. Parse Transform | Master | 2-3 weeks | ★★★★★ | ★★★★★ |
| 18. Distributed Game | Expert | 4-6 weeks | ★★★★★ | ★★★★★ |
| 19. Message Queue | Master | 2-3 months | ★★★★★ | ★★★★★ |
Recommendation
Given your Elixir background, I recommend this order:
Week 1-2: Syntax Transition
Start with Project 1 (Calculator REPL) and Project 2 (INI Parser). These are fast and will cement Erlang syntax in your muscle memory. The goal is to stop thinking “how do I write this in Erlang?” and just write it.
Week 3-4: Concurrency Without Training Wheels
Jump to Project 4 (Chat Server without OTP). This is the “aha!” project. You know how GenServer works in Elixir—now build it from raw processes. When you’re done, you’ll truly understand what OTP provides.
Week 5-6: OTP Mastery
Do Project 7 (Build Your Own GenServer). This demystifies OTP completely. Then Project 8 (Supervision Visualizer) to understand production system introspection.
After that: Follow your interests
- Want to understand BEAM deeply? → Projects 11-14
- Want to build distributed systems? → Projects 9, 18
- Want to push Erlang’s limits? → Projects 15-17
- Want the full journey? → Do them all, ending with Project 19
Essential Resources
Books (in learning order)
- “Learn You Some Erlang for Great Good!” by Fred Hébert - Free online, humorous, excellent for syntax
- “Programming Erlang” by Joe Armstrong - Written by Erlang’s creator, the definitive intro
- Chapters 1-10 for basics, 11-20 for OTP
- “Erlang and OTP in Action” by Logan, Merritt, Carlsson - Production patterns and best practices
- “Designing for Scalability with Erlang/OTP” by Cesarini & Vinoski - Advanced OTP design
- When you’re ready to build serious systems
- “The BEAM Book” by Erik Stenman - VM internals
Online Resources
- Erlang Official Documentation
- Erlang-BEAM-Links Collection - Curated VM internals resources
- A Brief BEAM Primer - Official BEAM introduction
- Erlang Solutions Blog - BEAM deep dives
Summary
| # | Project | Main Language |
|---|---|---|
| 1 | Erlang Calculator REPL | Erlang |
| 2 | INI File Parser & Writer | Erlang |
| 3 | Markdown to HTML Converter | Erlang |
| 4 | Chat Server Without OTP | Erlang |
| 5 | Process Pool Manager | Erlang |
| 6 | Rate Limiter with Token Bucket | Erlang |
| 7 | Build Your Own GenServer | Erlang |
| 8 | Supervision Tree Visualizer | Erlang |
| 9 | Distributed Key-Value Store with Mnesia | Erlang |
| 10 | State Machine with gen_statem | Erlang |
| 11 | BEAM Bytecode Analyzer | Erlang |
| 12 | Custom BEAM Tracer | Erlang |
| 13 | Process Heap Inspector | Erlang |
| 14 | Scheduler Utilization Monitor | Erlang |
| 15 | Erlang NIF for Fast JSON | Erlang + C |
| 16 | Hot Code Upgrade System | Erlang |
| 17 | Parse Transform Macro System | Erlang |
| 18 | Distributed Erlang Game Server | Erlang |
| 19 | Production-Ready Message Queue (Mini-RabbitMQ) | Erlang |
By the end of these projects, you won’t just “know Erlang”—you’ll understand why Erlang exists, what problems it uniquely solves, and how to build systems that run forever.