0 XP
Start with one lesson note or one code drill to unlock your first progress signal.
Feedback Loop
The page now rewards effort, not just perfect answers. Write notes, solve drills, and build momentum.
Start with one lesson note or one code drill to unlock your first progress signal.
Open a module, write one thought in the note field, then run one coding drill.
0 quiz wins and 0 checked milestones completed.
Lessons
That is how understanding replaces memorization.
Erlang clearly separates atoms like ok, numbers like 42, and binaries like <<"hi">>.
Pattern matching is not just comparison. It is structured unpacking of values.
Name three values with different shapes and read them back aloud.
Then match one success tuple and one error tuple until the left-hand pattern feels natural.
If you can explain why {ok, User} = Value may fail, you are ready for the next module.
Erlang clearly separates atoms like ok, numbers like 42, and binaries like <<"hi">>.
Memory line: An atom often describes meaning, a binary often carries actual data.
State = idle,
Count = 3,
Name = <<"Ada">>.Pattern matching is not just comparison. It is structured unpacking of values.
Memory line: Erlang always asks, Does the shape fit?
{ok, User} = {ok, #{name => <<"Ada">>}}.Which parts of {error, timeout} can you extract directly with pattern matching?
Start from the tuple shape first. Match {ok, User}, not just User, so Erlang checks the success case and binds the second value.
A function can have multiple shapes. Erlang checks them from top to bottom.
Guards make rules readable and keep logic out of the body when possible.
Start every recursive function by writing the empty or simple case first.
Then add the [H | T] case and say what becomes smaller on each call.
If you can sketch sum/1 and explain why it stops, recursion is starting to click.
A function can have multiple shapes. Erlang checks them from top to bottom.
Memory line: First pattern, then guard, then function body.
size([]) -> 0;
size([_ | Tail]) -> 1 + size(Tail).Guards make rules readable and keep logic out of the body when possible.
Memory line: Guards clarify cases before work begins.
kind(N) when N > 0 -> positive;
kind(0) -> zero;
kind(_) -> negative.In Erlang, recursion is the normal way to move through data.
Memory line: Every recursion needs a start, a step, and an end.
sum([]) -> 0;
sum([H | T]) -> H + sum(T).Mentally design a function all_even/1 that returns true only if every element is even.
Think in two clauses: the empty list returns a simple value, and the non-empty list splits into head and tail.
An Erlang process is extremely lightweight and owns its own state.
With !, you send messages asynchronously into a process mailbox.
Write the message format before writing the receive block.
For each branch, decide whether state changes, a reply is sent, or both.
If you can describe increment and {get, From} as a protocol, you are learning the Erlang way.
An Erlang process is extremely lightweight and owns its own state.
Memory line: Do not share state, send messages.
Pid = spawn(fun loop/0).With !, you send messages asynchronously into a process mailbox.
Memory line: Sending is immediate, processing comes later.
Pid ! {self(), ping}.A process is often modeled as a loop: receive a message, react, continue.
Memory line: Behavior emerges from receiving plus the next loop.
receive
stop -> ok;
Msg -> io:format("~p~n", [Msg]), loop()
end.How would you design a counter process that reacts to increment and get?
A counter loop usually needs one branch that changes state and another branch that replies to the caller before looping again.
In Erlang, error handling is often structural: one process may crash while another restores it.
GenServer encapsulates state and standardizes the message flow.
Say which part belongs to the worker and which part belongs to supervision.
Then map a simple request into handle_call plus a reply tuple.
If you can justify one_for_one in plain language, you are past the syntax stage.
In Erlang, error handling is often structural: one process may crash while another restores it.
Memory line: Not every error should be fixed inside the same process.
link(WorkerPid).GenServer encapsulates state and standardizes the message flow.
Memory line: OTP gives you structure for repeating process patterns.
handle_call(get, _From, State) ->
{reply, State, State}.Supervisors decide whether a single worker or an entire group should restart.
Memory line: Resilience is a tree structure, not an accident.
{one_for_one, 5, 10}When would a one_for_one strategy make more sense than one_for_all?
A minimal GenServer callback should name the message, include the caller slot, and return both reply and next state.
Multiple processes can handle the same kind of task in parallel without blocking one another.
State should ideally live inside a single process and never be shared directly.
Choose exactly one process that owns the map or queue.
Then define which messages enter, which replies leave, and where validation happens.
If each process has one job and one state boundary, your design is getting professional.
Multiple processes can handle the same kind of task in parallel without blocking one another.
Memory line: Parallelism needs work distribution, not just more CPU.
Workers = [spawn(fun worker/0) || _ <- lists:seq(1, 4)].State should ideally live inside a single process and never be shared directly.
Memory line: Isolation reduces side effects.
store_loop(State) ->
receive
{put, Key, Val} -> store_loop(maps:put(Key, Val, State));
{get, From, Key} -> From ! maps:get(Key, State), store_loop(State)
end.Sketch a three-step pipeline with one process for receiving, one for validating, and one for storing.
Show that one process owns the map. One message should write into state, another should read and answer the caller.
A node is a running Erlang instance that can cooperate with other nodes.
Messages can cross nodes as long as naming and connectivity are correct.
Write the success path first, then add the timeout branch immediately.
Decide what the caller should do after timeout: retry, log, or escalate.
If timeout is part of your first draft, you are starting to think like a distributed systems engineer.
A node is a running Erlang instance that can cooperate with other nodes.
Memory line: Distribution starts with explicit connection, not magic.
net_kernel:connect_node('worker@host').Messages can cross nodes as long as naming and connectivity are correct.
Memory line: A distributed system is only as good as its failure assumptions.
{service, 'worker@host'} ! ping.In distributed systems, timeouts, retries, monitoring, and ownership matter.
Memory line: Strong systems plan for network failure from the start.
receive
reply -> ok
after 2000 ->
timeout
end.What kinds of failures can happen when a remote node is temporarily unreachable?
For distributed work, show both outcomes: a normal reply branch and a timeout branch inside receive ... after.
Practice Studio
These drills are lightweight browser checks, not a full compiler, but they push learners into active construction instead of only reading.
Write one Erlang line that extracts User from a value shaped like {ok, #{name => <<"Ada">>}}.
Write a recursive sum/1 function with a base case for [] and a recursive step for [H | T].
Sketch a process loop that handles an increment message and a get message that replies back to From.
Account