Basic GenServer Implementation

Welcome to your first hands-on GenServer lesson in this course on OTP Fundamentals. We will build a simple Stack server that holds state, accepts updates, and responds to requests using standard GenServer patterns. You will see how to start a GenServer, initialize state, expose a small public API, and handle synchronous vs. asynchronous messages. GenServer is part of Elixir/OTP, so there is nothing extra to install.

Start and Initialize the Server

Here is how we define the module, start the process, and set the initial state:

Explanation:

  • use GenServer brings in the GenServer behavior.
  • start_link/1 boots the server and registers it under the module name (name: __MODULE__), so you do not need to pass a PID later.
  • init/1 sets the initial state to the provided list.
Public API: Push (Cast) and Pop (Call)

We provide a clean API that hides GenServer details from callers:

Explanation:

  • push/1 uses GenServer.cast/2, which is asynchronous (fire-and-forget). It returns immediately.
  • pop/0 uses GenServer.call/2, which is synchronous. It waits for a reply from the server.
  • Both target the registered name (__MODULE__), not a PID.

Note: GenServer.call/2 has a default timeout of 5000 milliseconds (5 seconds). If the server does not reply within this time, a GenServer.CallError (timeout) is raised. You can override the timeout by calling GenServer.call(__MODULE__, :pop, 10_000) for a 10-second timeout. In production code, you should consider handling possible timeout errors using try/rescue or by setting an appropriate timeout for your use case.

Handle Messages and Manage State

These callbacks update and read the server’s state:

Explanation:

  • handle_cast/2 processes the async push by prepending the item to the list. It returns {:noreply, new_state}.
  • handle_call/3 for :pop matches two cases:
    • Non-empty stack [head | tail]: replies with head and keeps tail as the new state.
    • Empty stack []: replies with nil and keeps the state unchanged.
Run and Observe

Explanation:

  • We start with an empty stack, push 1, 2, and 3, then pause briefly with Process.sleep/50 to allow the asynchronous cast operations to complete before popping.
  • This ensures that the pop calls reflect the expected stack state and you see 3 and then 2 printed.
  • The final Process.sleep/100 gives time for any remaining async casts to be handled before the script exits.
Summary

You now have a working mental model for GenServer: booting and initialization for setup, asynchronous updates for fire-and-forget tasks, synchronous requests for retrieving data, and callbacks for managing state. When you are ready, head to the practice section and apply what you have learned to reinforce these patterns.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal