Elixir's killer feature is concurrency. BEAM processes are lighter than OS threads — you can spawn a million of them. Today you learn to spawn processes, send messages, and build supervised process trees.
spawn/1 creates a new process running a function. Each process has a unique PID. Processes communicate only by sending messages — no shared memory. send(pid, message) sends; receive do...end receives. self() returns the current PID. Process.alive?(pid) checks if a process is running. spawn_link/1 creates a bidirectional link — if one crashes, the other crashes too.
GenServer (Generic Server) is a behaviour (interface) that implements a client-server process pattern. You define handle_call/3 (synchronous), handle_cast/2 (async), init/1, and handle_info/2. GenServer.start_link/2 starts it; GenServer.call/2 sends synchronous messages; GenServer.cast/2 sends async. All state lives in the GenServer process — no shared state across processes, no locks needed.
A Supervisor monitors child processes and restarts them on crash. Restart strategies: one_for_one (restart only the crashed child), one_for_all (restart all if one crashes), rest_for_one (restart crashed + those started after it). Supervisors form trees — the top-level supervisor rarely crashes, and lower-level process crashes are contained and auto-recovered. This is the OTP fault-tolerance model.
defmodule Counter do
use GenServer
# Client API
def start_link(initial \\ 0) do
GenServer.start_link(__MODULE__, initial, name: __MODULE__)
end
def increment, do: GenServer.cast(__MODULE__, :increment)
def decrement, do: GenServer.cast(__MODULE__, :decrement)
def value, do: GenServer.call(__MODULE__, :value)
# Server callbacks
@impl true
def init(count), do: {:ok, count}
@impl true
def handle_call(:value, _from, count) do
{:reply, count, count}
end
@impl true
def handle_cast(:increment, count), do: {:noreply, count + 1}
def handle_cast(:decrement, count), do: {:noreply, count - 1}
end
# Usage
{:ok, _pid} = Counter.start_link(0)
Counter.increment()
Counter.increment()
Counter.increment()
IO.puts Counter.value() # 3
Build a rate limiter GenServer that allows at most N requests per minute per client ID. If the limit is exceeded, return {:error, :rate_limited}. Test with 5 concurrent processes each making 20 requests.