# Name dynamic processes with `:via` tuples

The moment you need one process per game, per room, or per upload, the naming problem shows up — and the tempting fix is `String.to_atom("game_#{id}")`. That builds an atom from user input, which is never garbage collected and will eventually take down the VM. It also leaves the name registered against nothing when the process dies. `Registry` solves both at once.

```elixir
defmodule MyApp.Game do
  use GenServer

  def start_link(id), do: GenServer.start_link(__MODULE__, id, name: via(id))

  def score(id), do: GenServer.call(via(id), :score)

  defp via(id), do: {:via, Registry, {MyApp.GameRegistry, {:game, id}}}

  @impl GenServer
  def init(id), do: {:ok, %{id: id, score: 0}}

  @impl GenServer
  def handle_call(:score, _from, state), do: {:reply, state.score, state}
end
```

A `:via` tuple is accepted anywhere a process name is — `GenServer.start_link/3`, `GenServer.call/3`, `GenServer.cast/2`, `DynamicSupervisor.start_child/2`. The key can be any term, so `{:game, id}` works directly with no string interpolation and no atom creation.

Start the registry in your supervision tree:

```elixir
children = [
  {Registry, keys: :unique, name: MyApp.GameRegistry},
  {DynamicSupervisor, name: MyApp.GameSupervisor, strategy: :one_for_one}
]
```

## Deregistration is automatic

This is the part that makes it worth the indirection. `Registry` monitors every registered process and removes the entry when it exits — no cleanup callback, no stale name:

```elixir
{:ok, pid} = MyApp.Game.start_link("abc")

Registry.lookup(MyApp.GameRegistry, {:game, "abc"})
# => [{#PID<0.123.0>, nil}]

GenServer.stop(pid)

Registry.lookup(MyApp.GameRegistry, {:game, "abc"})
# => []
```

With atom names you would have to unregister by hand, and a crash would skip that code entirely.

## Start-or-find

Because registration happens inside `start_link/3`, a duplicate start fails cleanly with the existing pid, which is exactly what you need for lazy process creation:

```elixir
def ensure_started(id) do
  case DynamicSupervisor.start_child(MyApp.GameSupervisor, {MyApp.Game, id}) do
    {:ok, pid} -> {:ok, pid}
    {:error, {:already_started, pid}} -> {:ok, pid}
    error -> error
  end
end
```

No `lookup`-then-`start` race — the registry decides the winner atomically.

## Values, not just names

The third element of the registration key can carry metadata, and `keys: :duplicate` turns the same registry into a pubsub-style index:

```elixir
Registry.register(MyApp.GameRegistry, {:lobby, lobby_id}, %{role: :player})

Registry.dispatch(MyApp.GameRegistry, {:lobby, lobby_id}, fn entries ->
  for {pid, %{role: :player}} <- entries, do: send(pid, :starting)
end)
```

The rule of thumb: atoms are for names you write in source code, `Registry` is for names you compute at runtime. Anything derived from an id, a slug, or user input belongs in the second category.

[`Registry` docs](https://hexdocs.pm/elixir/Registry.html)


---

Created by: almirsarajcic
Date: August 23, 2026
URL: https://elixirdrops.net/d/BaQytUDT
