# Elixir Expert

> Expert-level Elixir, Phoenix, OTP, and concurrent systems. Use when the user mentions Phoenix, OTP, Erlang, concurrent, or functional, or when the task involves Elixir Fundamentals or Phoenix Framework.

- Skill: `personamanagmentlayer/elixir-expert` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add personamanagmentlayer/elixir-expert`
- Raw SKILL.md: https://api.skillmd.com/api/skills/personamanagmentlayer/elixir-expert/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: personamanagmentlayer (https://skillmd.com/u/personamanagmentlayer)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/personamanagmentlayer/elixir-expert

---


# Elixir Expert

Expert guidance for Elixir programming, Phoenix framework, OTP, and building highly concurrent, fault-tolerant systems.

## Core Concepts

### Elixir Fundamentals

- Functional programming
- Pattern matching
- Immutability
- Pipe operator
- Modules and functions
- Structs and maps

### OTP (Open Telecom Platform)

- GenServer
- Supervisors
- Applications
- Tasks
- Agents
- ETS (Erlang Term Storage)

### Phoenix Framework

- Contexts and schemas
- LiveView
- Channels (WebSockets)
- Plug
- Ecto (database)
- Testing

## Elixir Basics

```elixir
# Pattern matching
{:ok, result} = {:ok, 42}
[head | tail] = [1, 2, 3, 4]

# Functions
defmodule Math do
  def add(a, b), do: a + b

  def factorial(0), do: 1
  def factorial(n) when n > 0, do: n * factorial(n - 1)
end

# Pipe operator
"hello"
|> String.upcase()
|> String.reverse()
# "OLLEH"

# Anonymous functions
add = fn a, b -> a + b end
add.(1, 2)  # 3

# Capture operator
add = &(&1 + &2)
Enum.map([1, 2, 3], &(&1 * 2))

# Structs
defmodule User do
  defstruct [:id, :name, :email, age: 0]
end

user = %User{id: "123", name: "John", email: "john@example.com"}

# Pattern matching with structs
%User{name: name} = user

# Maps
user_map = %{id: "123", name: "John", email: "john@example.com"}
%{name: name} = user_map

# Conditionals
if user.age >= 18 do
  "Adult"
else
  "Minor"
end

# Case
case user.role do
  :admin -> "Administrator"
  :user -> "Regular user"
  _ -> "Unknown"
end

# Cond
cond do
  user.age < 13 -> "Child"
  user.age < 18 -> "Teen"
  user.age < 65 -> "Adult"
  true -> "Senior"
end

# With
with {:ok, user} <- fetch_user(id),
     {:ok, posts} <- fetch_posts(user.id) do
  {:ok, user, posts}
else
  {:error, reason} -> {:error, reason}
end
```

## GenServer

```elixir
defmodule UserCache do
  use GenServer

  # Client API
  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, %{}, opts)
  end

  def get(server, key) do
    GenServer.call(server, {:get, key})
  end

  def put(server, key, value) do
    GenServer.cast(server, {:put, key, value})
  end

  # Server Callbacks
  @impl true
  def init(initial_state) do
    {:ok, initial_state}
  end

  @impl true
  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  @impl true
  def handle_cast({:put, key, value}, state) do
    {:noreply, Map.put(state, key, value)}
  end

  @impl true
  def handle_info(:cleanup, state) do
    # Periodic cleanup
    {:noreply, state}
  end
end

# Usage
{:ok, cache} = UserCache.start_link()
UserCache.put(cache, :user1, %User{name: "John"})
UserCache.get(cache, :user1)
```

## Supervisors

```elixir
defmodule MyApp.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      # Database connection pool
      {MyApp.Repo, []},

      # GenServer workers
      {UserCache, name: UserCache},
      {SessionManager, name: SessionManager},

      # Task supervisor
      {Task.Supervisor, name: MyApp.TaskSupervisor},

      # Phoenix endpoint
      MyAppWeb.Endpoint
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

# Custom supervisor
defmodule WorkerSupervisor do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    children = [
      {Worker, id: :worker1, restart: :permanent},
      {Worker, id: :worker2, restart: :temporary},
      {Worker, id: :worker3, restart: :transient}
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end
```

## Ecto

```elixir
# Schema
defmodule MyApp.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :email, :string
    field :age, :integer
    field :role, Ecto.Enum, values: [:admin, :user, :guest]

    has_many :posts, MyApp.Blog.Post

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :age, :role])
    |> validate_required([:name, :email])
    |> validate_format(:email, ~r/@/)
    |> validate_number(:age, greater_than: 0, less_than: 150)
    |> unique_constraint(:email)
  end
end

# Queries
import Ecto.Query

# Simple query
Repo.all(from u in User, where: u.age > 18)

# With preload
Repo.all(from u in User, preload: [:posts])

# Complex query
query =
  from u in User,
    join: p in assoc(u, :posts),
    where: u.age > 18,
    group_by: u.id,
    having: count(p.id) > 5,
    select: {u, count(p.id)}

Repo.all(query)

# Dynamic queries
defmodule UserQueries do
  import Ecto.Query

  def base_query, do: from(u in User)

  def filter_by_age(query, min_age) do
    from u in query, where: u.age >= ^min_age
  end

  def filter_by_role(query, role) do
    from u in query, where: u.role == ^role
  end

  def with_posts(query) do
    from u in query, preload: [:posts]
  end
end

# Usage
User
|> UserQueries.base_query()
|> UserQueries.filter_by_age(18)
|> UserQueries.filter_by_role(:admin)
|> UserQueries.with_posts()
|> Repo.all()
```

## Phoenix LiveView

```elixir
defmodule MyAppWeb.UserLive.Index do
  use MyAppWeb, :live_view

  alias MyApp.Accounts

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket) do
      Phoenix.PubSub.subscribe(MyApp.PubSub, "users")
    end

    {:ok, assign(socket, users: Accounts.list_users())}
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    user = Accounts.get_user!(id)
    {:ok, _} = Accounts.delete_user(user)

    {:noreply, assign(socket, users: Accounts.list_users())}
  end

  @impl true
  def handle_info({:user_created, user}, socket) do
    {:noreply, update(socket, :users, fn users -> [user | users] end)}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <h1>Users</h1>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          <%= for user <- @users do %>
            <tr>
              <td><%= user.name %></td>
              <td><%= user.email %></td>
              <td>
                <button phx-click="delete" phx-value-id={user.id}>
                  Delete
                </button>
              </td>
            </tr>
          <% end %>
        </tbody>
      </table>
    </div>
    """
  end
end
```

## Concurrency

```elixir
# Tasks
task = Task.async(fn -> expensive_computation() end)
result = Task.await(task)

# Parallel processing
results =
  [1, 2, 3, 4, 5]
  |> Enum.map(&Task.async(fn -> process(&1) end))
  |> Enum.map(&Task.await/1)

# Task supervisor
Task.Supervisor.async_nolink(MyApp.TaskSupervisor, fn ->
  perform_background_job()
end)

# Agents
{:ok, agent} = Agent.start_link(fn -> %{} end)
Agent.update(agent, fn state -> Map.put(state, :key, "value") end)
Agent.get(agent, fn state -> Map.get(state, :key) end)

# ETS
table = :ets.new(:my_table, [:set, :public])
:ets.insert(table, {:key, "value"})
:ets.lookup(table, :key)
```

## Best Practices

### Elixir

- Use pattern matching extensively
- Leverage pipe operator
- Write small, focused functions
- Handle all pattern match cases
- Use proper error handling
- Follow naming conventions
- Write documentation

### OTP

- Use GenServers for state
- Implement supervisors properly
- Design for failure
- Keep processes focused
- Use process monitoring
- Implement timeouts
- Log appropriately

### Phoenix

- Follow context boundaries
- Use changesets for validation
- Implement proper authentication
- Handle errors gracefully
- Test thoroughly
- Use LiveView for interactivity
- Optimize queries

## Anti-Patterns

❌ Using mutable state
❌ Long-running GenServer calls
❌ Not supervising processes
❌ Ignoring errors
❌ Large modules
❌ N+1 query problems
❌ Not using pattern matching

## Reference Documentation

Detailed material lives alongside this skill and is read on demand:

- [Phoenix Framework](references/PHOENIX_FRAMEWORK.md)

## Resources

- Elixir: https://elixir-lang.org/
- Phoenix: https://www.phoenixframework.org/
- Hex: https://hex.pm/
- Elixir School: https://elixirschool.com/
- Programming Elixir (book)

