Reading — step 1 of 5
Learn
~1 min readOTP Building Blocks
GenServer is OTP's most-used behaviour — a server process with sync call, async cast, and lifecycle hooks. Replaces hand-rolled receive loops.
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__, :inc)
def value, do: GenServer.call(__MODULE__, :get)
# Server callbacks
def init(initial), do: {:ok, initial}
def handle_call(:get, _from, state), do: {:reply, state, state}
def handle_cast(:inc, state), do: {:noreply, state + 1}
end
This pattern is THE Elixir convention — client API + server callbacks in one module.
Run:
Counter.start_link()
Counter.increment()
Counter.increment()
Counter.increment()
Counter.value() # 3
Differences:
call— synchronous. Caller blocks until reply. Use for queries.cast— fire-and-forget. Returns:okimmediately. Use for actions where caller doesn't need a response.
Server callback return shapes:
{:reply, reply, new_state}— for handle_call{:noreply, new_state}— no reply (or used in handle_cast){:stop, reason, reply, state}/{:stop, reason, state}— terminate
Lifecycle:
init(args)— runs on startup. Return{:ok, state}or{:stop, reason}.handle_info(msg, state)— for messages that aren't calls/casts (e.g., timeouts)terminate(reason, state)— called on normal/abnormal shutdown (best-effort cleanup)
Why GenServer over manual processes:
- Battle-tested message passing
- Hooks into supervision (next lesson)
- Built-in tracing, hot code reload, debugging
- Standard error handling —
:sysmodule gives you:sys.get_state(pid)for inspection
99% of stateful Elixir code uses GenServer. The simple spawn + receive pattern is for understanding, not production.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…