100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

gen_server Behaviour

Master the gen_server OTP behaviour — the standard pattern for building stateful, message-handling server processes with synchronous and asynchronous calls.

Concurrency & OTPIntermediate10 min readJul 10, 2026
Analogies

Why gen_server?

Writing a raw process that loops on receive, matches messages, tracks state, and replies to callers correctly is easy to get subtly wrong — missing a catch-all clause, forgetting to loop with the new state, or mishandling replies are common bugs. gen_server is an OTP behaviour that provides this receive loop, state threading, and reply mechanism as a well-tested, standardized library, so a developer only has to implement a handful of callback functions describing what should happen for each kind of request.

🏏

Cricket analogy: Just as a standardized net-bowling machine setup at every franchise nets session removes the need for each team to reinvent how to feed balls at a batter, gen_server abstracts away the repetitive receive-loop boilerplate so every OTP server process follows the same reliable pattern.

The Callback Contract: init, handle_call, handle_cast, handle_info

A gen_server module implements init/1, which runs once at startup and returns the initial state as {ok, State}; handle_call/3, invoked for synchronous requests sent via gen_server:call/2,3, which must return something like {reply, Reply, NewState}; handle_cast/2, invoked for asynchronous requests sent via gen_server:cast/2, returning {noreply, NewState}; and handle_info/2, invoked for any other message that arrives in the mailbox outside the call/cast protocol, such as a monitor's 'DOWN' message.

🏏

Cricket analogy: init/1 is like a team's pre-match warm-up setting the starting XI (initial state); handle_call is answering a direct question from the umpire that needs an immediate ruling reply; handle_cast is signaling a substitution to the dugout without waiting for acknowledgment; handle_info is reacting to an unplanned rain interruption announcement.

erlang
-module(counter_server).
-behaviour(gen_server).
-export([start_link/0, increment/0, get_count/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).

increment() ->
    gen_server:cast(?MODULE, increment).

get_count() ->
    gen_server:call(?MODULE, get_count).

init(InitialCount) ->
    {ok, InitialCount}.

handle_call(get_count, _From, State) ->
    {reply, State, State}.

handle_cast(increment, State) ->
    {noreply, State + 1}.

handle_info(_Msg, State) ->
    {noreply, State}.

call vs cast: Synchronous vs Asynchronous Requests

gen_server:call/2,3 sends a request and blocks the calling process until a reply arrives or a timeout expires — 5000 milliseconds by default, configurable via a third argument or set to infinity. gen_server:cast/2, by contrast, sends a request and returns immediately without waiting for it to be handled at all, making it appropriate for notifications where the caller doesn't need confirmation. A subtle danger is calling gen_server:call from inside a handle_call callback on a process that, directly or indirectly, calls back into the original caller — this creates a circular wait that hangs both processes until the call times out.

🏏

Cricket analogy: Asking the third umpire for a review and waiting on the big screen for the decision before play resumes mirrors gen_server:call's blocking wait for a reply; a captain waving to signal a field change without waiting for confirmation mirrors cast; but if the third umpire tried reviewing their own decision it would freeze the game, like a process calling itself.

Never let handle_call for process A make a gen_server:call to process B if there's any chance B's own handling path calls back into A synchronously — this circular wait will hang both processes until the call timeout fires (default 5000ms), surfacing as a confusing timeout exception far from the real cause.

State Management and Hot Code Upgrades

Every gen_server callback threads the process's state explicitly through its return value — {reply, Reply, NewState}, {noreply, NewState}, and so on — rather than mutating a shared variable, keeping state changes explicit and traceable. Beyond ordinary state updates, gen_server also supports the optional code_change/3 callback, which is invoked during a hot code upgrade to transform a running process's existing state into the shape the new code version expects, allowing the server to keep running — and keep its accumulated state — without ever stopping.

🏏

Cricket analogy: A scorer who updates the scoreboard after every single ball, carrying forward the exact same running total into the next delivery, mirrors how gen_server threads state immutably through each callback's return tuple; switching to a new digital scoreboard system mid-match without stopping the game mirrors code_change/3's hot upgrade.

gen_server:call/2 defaults to a 5000ms timeout; pass gen_server:call(Server, Request, Timeout) with an explicit value, or infinity, when an operation is known to take longer — but avoid infinity for calls to servers you don't fully trust, since it removes your ability to fail fast.

  • gen_server is an OTP behaviour that abstracts the standard receive-loop pattern for stateful server processes.
  • init/1 initializes state; handle_call/3 handles synchronous requests; handle_cast/2 handles asynchronous requests; handle_info/2 handles out-of-band messages.
  • gen_server:call/2,3 blocks the caller until a reply arrives or a timeout (default 5000ms) expires.
  • gen_server:cast/2 is fire-and-forget and returns immediately without waiting for the server to process it.
  • Calling gen_server:call on a process that then calls back into the original caller synchronously can deadlock.
  • State is threaded immutably through the return tuples of each callback ({reply, Reply, NewState}, {noreply, NewState}, etc.).
  • code_change/3 allows a running gen_server's state to be transformed during a hot code upgrade without stopping the process.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ErlangStudyNotes#GenServerBehaviour#Gen#Server#Behaviour#Callback#StudyNotes#SkillVeris#ExamPrep