Skip to content
gen_statem — State Machine Behaviour
step 1/7

Reading — step 1 of 7

Learn

~2 min readgen_statem, Hot Code Reload, Performance

While gen_server handles general stateful processes, gen_statem specializes in finite state machines. Erlang/OTP's docs treat it as the modern replacement for the older gen_fsm.

When use gen_statem

When your server has explicit STATES with different behavior in each:

  • TCP connection (closed → connecting → established → closing)
  • Order processing (cart → confirmed → shipped → delivered)
  • Authentication flow (anonymous → challenged → authenticated)
  • Game session (waiting → playing → paused → finished)

The state name dispatches to different handler functions. Cleaner than a switch over state field in a gen_server.

Module skeleton

-module(door).
-behaviour(gen_statem).
-export([start_link/0, knock/0, open/0, close/0]).
-export([init/1, callback_mode/0, handle_event/4, terminate/3]).

%% Public API
start_link() -> gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).
knock() -> gen_statem:call(?MODULE, knock).
open() -> gen_statem:cast(?MODULE, open).
close() -> gen_statem:cast(?MODULE, close).

%% gen_statem callbacks
init([]) -> {ok, closed, none}.

callback_mode() -> handle_event_function.

%% Closed state
handle_event(cast, open, closed, Data) ->
    io:format("opening the door~n"),
    {next_state, opened, Data};
handle_event({call, From}, knock, closed, Data) ->
    {next_state, closed, Data, [{reply, From, knock_knock}]};

%% Opened state
handle_event(cast, close, opened, Data) ->
    io:format("closing the door~n"),
    {next_state, closed, Data};
handle_event({call, From}, knock, opened, Data) ->
    {next_state, opened, Data, [{reply, From, already_open}]};

handle_event(_Event, _Msg, State, Data) ->
    {next_state, State, Data}.

terminate(_Reason, _State, _Data) -> ok.

Two callback modes

state_functions — one function per state:

closed(EventType, EventContent, Data) -> ...
opened(EventType, EventContent, Data) -> ...

handle_event_function (shown above) — single handler dispatched on state:

handle_event(EventType, EventContent, State, Data) -> ...

Pick one in callback_mode/0. State functions read better for many states; handle_event scales for complex dispatch.

Event types

  • {call, From} — synchronous call from gen_statem:call/2
  • cast — async from gen_statem:cast/2
  • info — generic message (from !)
  • internal — events generated within the state machine
  • timeout — event timer fired
  • state_timeout — state-specific timeout

Common return values

{next_state, NewState, NewData}
{next_state, NewState, NewData, Actions}
{keep_state, NewData}
{keep_state_and_data, Actions}
{stop, Reason, NewData}

Actions can include replies (for calls), timers, postpone, etc.

When NOT to use gen_statem

  • Simple stateful servers with no distinct states — use gen_server
  • Pure functional state — no process needed at all
  • Very few states — a gen_server with a state field is fine

Comparison

gen_servergen_statem
StateOne blobNamed states with own handlers
DispatchManualAutomatic by state
VerbosityLess for simple serversLess for complex state machines
Use caseWorkers, caches, registriesProtocol handlers, workflows, games

Common mistakes

  • gen_statem for non-state-machine code — overkill. gen_server is simpler when there's no state-based dispatch.
  • Forgetting callback_mode/0 — required, or the module won't start.
  • Mixing event types — calls and casts behave differently. Pick the right one for each operation.
  • Returning wrong tuple shape — gen_statem expects specific tuples. Refer to docs for exact return values.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…