Chat room using GenServer It seems odd that state must be passed around all the time. Can't State module hold itself, so I could do something like

state = %State{size: size}
case state.is_full? do
    ...


Here state is always passed. I suppose this is how it is in FP?

defmodule Chatter.Room do
  use GenServer  

  defmodule State do
    defstruct size: 3, players: []

    def is_full?(state) do
      Enum.count(state.players) &gt;= state.size
    end

    def add_player(state, player) do
      case State.is_full?(state) do
        true -&gt; {:room_is_full, state}
        _    -&gt; {:ok, append_player(state, player)}
      end
    end

    defp append_player(state, player) do
      %{state | players: state.players ++ [player]}
    end
  end

  def open(size) do
    {:ok, pid} = GenServer.start_link(__MODULE__, %State{size: size})
    pid
  end

  def join(room, user) do
    GenServer.call(room, {:join, user})
  end

  def players(room) do
    GenServer.call(room, :players)
  end


  def init(state) do
    {:ok, state}
  end

  def handle_call(:players, _from, state) do
    {:reply, state.players, state}
  end

  def handle_call({:join, player}, _from, state) do
    case State.add_player(state, player) do
      {:ok, state} -&gt; {:reply, :ok, state}
      {error, state} -&gt; {:reply, error, state}
    end
  end
end
Short answer -- yes, data including state is immutable.

In traditional functional programming idiom data is immutable.  With this in mind, GenServer stores the current state internally.  When you implement the callback functions (handle_call, handle_cast, ...) the current state is passed to your callback function.  You then return a new state which then replaces the existing state inside the GenServer.  Depending on the data structure used for maintaining your state, only a portion of the new data may be "deeply" copied.

If I relate this directly to your question, I would not implement state as you have.  The GenServer wraps the state for you.  A more erlang/elixirish outline would be something like:

defmodule ModuleName do
  # api functions 
  # place calls to cast, call, here
  def is_full?(server) do
    GenServer.call(server, {:check_full})
  end

  # callbacks
  # match ":check_full" call
  def handle_call({:check_full}, _from, state) do
    # perform your full check actions here
  end

  # match an ":add_user" call
  def handle_call({:add_user, {user_data}}, _from, state) do
    # perform your add_user actions here
  end

  # catch all
  def handle_call(request, _from, state) do
    # do something here, if you want, or omit as suits your style
  end
end


Certainly, you can also use guards instead of matching or use one giant handle_call function with a case or cond to separate out the pieces.  The method used above is clean and easy to understand.

I may have missed the point of the question --- the short answer was, yes, we pass the state around.  The longer answer was the GenServer can keep track of it for you.