Chat room using GenServer
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.