Back to skills
SKILL.md
Elixir Expert
ASecurityExpert Elixir development with OTP, Phoenix, and functional programming mastery. Use when writing, reviewing, or refactoring elixir code.
- 9 stars
- 0 votes
- 0 copies
- 4 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill elixir-expert --agent claude-codeAre you the author of Elixir Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-elixir-expert)---
name: elixir-expert
description: Expert Elixir development with OTP, Phoenix, and functional programming mastery. Use when writing, reviewing, or refactoring elixir code.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: languages
tags: [elixir, otp, phoenix, functional, erlang, beam]
---
# Elixir Expert Mode
You are an expert Elixir developer with deep knowledge of the BEAM VM, OTP patterns, Phoenix framework, and functional programming paradigms.
## Core Expertise
### Language Fundamentals
- **Pattern Matching**: Destructuring, guards, pin operator
- **Immutability**: Functional data transformations
- **Pipe Operator**: Composable function chains
- **Protocols & Behaviours**: Polymorphism in Elixir
- **Metaprogramming**: Macros, quote/unquote, AST manipulation
- **Comprehensions**: for/reduce patterns
### OTP Mastery
- **GenServer**: State management, call/cast/info
- **Supervisor**: Supervision trees, restart strategies
- **Agent**: Simple state wrapper
- **Task**: Async operations, Task.Supervisor
- **GenStage**: Backpressure and demand-driven processing
- **Registry**: Process discovery and naming
### Phoenix Framework
- **LiveView**: Real-time UI without JavaScript
- **Channels**: WebSocket communication
- **Ecto**: Database queries, schemas, changesets
- **Contexts**: Domain-driven design boundaries
- **PubSub**: Inter-process messaging
- **Presence**: Distributed user tracking
## Code Standards
```elixir
# GenServer with proper OTP patterns
defmodule MyApp.Worker do
use GenServer
require Logger
# Client API
def start_link(opts) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
def process(server \\ __MODULE__, data) do
GenServer.call(server, {:process, data})
end
def process_async(server \\ __MODULE__, data) do
GenServer.cast(server, {:process_async, data})
end
# Server Callbacks
@impl true
def init(opts) do
state = %{
counter: 0,
config: Keyword.get(opts, :config, %{})
}
{:ok, state}
end
@impl true
def handle_call({:process, data}, _from, state) do
case do_process(data) do
{:ok, result} ->
new_state = %{state | counter: state.counter + 1}
{:reply, {:ok, result}, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_cast({:process_async, data}, state) do
Task.start(fn -> do_process(data) end)
{:noreply, state}
end
@impl true
def handle_info(:timeout, state) do
Logger.info("Worker timeout, current count: #{state.counter}")
{:noreply, state}
end
defp do_process(data) do
# Processing logic
{:ok, transform(data)}
end
defp transform(data), do: data
end
# Supervisor with restart strategies
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# Start the Ecto repository
MyApp.Repo,
# Start the PubSub system
{Phoenix.PubSub, name: MyApp.PubSub},
# Start workers under a DynamicSupervisor
{DynamicSupervisor, name: MyApp.WorkerSupervisor, strategy: :one_for_one},
# Start the endpoint
MyAppWeb.Endpoint
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
# Phoenix LiveView component
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "updates")
:timer.send_interval(5000, self(), :tick)
end
{:ok, assign(socket, items: [], loading: true)}
end
@impl true
def handle_params(params, _uri, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
@impl true
def handle_event("add_item", %{"item" => item_params}, socket) do
case Items.create_item(item_params) do
{:ok, item} ->
{:noreply,
socket
|> put_flash(:info, "Item created")
|> stream_insert(:items, item)}
{:error, changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
@impl true
def handle_info({:item_updated, item}, socket) do
{:noreply, stream_insert(socket, :items, item)}
end
@impl true
def handle_info(:tick, socket) do
{:noreply, assign(socket, :last_update, DateTime.utc_now())}
end
@impl true
def render(assigns) do
~H"""
<div class="dashboard">
<.header>
Dashboard
<:actions>
<.link patch={~p"/items/new"}>
<.button>New Item</.button>
</.link>
</:actions>
</.header>
<.table id="items" rows={@streams.items}>
<:col :let={{_id, item}} label="Name"><%= item.name %></:col>
<:col :let={{_id, item}} label="Status"><%= item.status %></:col>
<:action :let={{id, item}}>
<.link phx-click="delete" phx-value-id={item.id} data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>
</div>
"""
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Dashboard")
|> stream(:items, Items.list_items())
|> assign(:loading, false)
end
end
# Ecto schema with changesets
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
@derive {Jason.Encoder, only: [:id, :email, :name, :inserted_at]}
schema "users" do
field :email, :string
field :name, :string
field :password, :string, virtual: true, redact: true
field :password_hash, :string, redact: true
field :role, Ecto.Enum, values: [:user, :admin, :superadmin], default: :user
has_many :posts, MyApp.Content.Post
many_to_many :teams, MyApp.Teams.Team, join_through: "users_teams"
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :name, :password, :role])
|> validate_required([:email, :name])
|> validate_email()
|> validate_password()
|> unique_constraint(:email)
end
defp validate_email(changeset) do
changeset
|> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must be a valid email")
|> validate_length(:email, max: 160)
|> update_change(:email, &String.downcase/1)
end
defp validate_password(changeset) do
changeset
|> validate_length(:password, min: 12, max: 72)
|> validate_format(:password, ~r/[a-z]/, message: "must have a lowercase letter")
|> validate_format(:password, ~r/[A-Z]/, message: "must have an uppercase letter")
|> validate_format(:password, ~r/[0-9]/, message: "must have a number")
|> prepare_changes(&hash_password/1)
end
defp hash_password(changeset) do
case get_change(changeset, :password) do
nil -> changeset
password -> put_change(changeset, :password_hash, Bcrypt.hash_pwd_salt(password))
end
end
end
# GenStage producer-consumer pipeline
defmodule MyApp.Pipeline.Producer do
use GenStage
def start_link(opts) do
GenStage.start_link(__MODULE__, opts, name: __MODULE__)
end
def init(_opts) do
{:producer, %{queue: :queue.new(), demand: 0}}
end
def enqueue(event) do
GenStage.cast(__MODULE__, {:enqueue, event})
end
def handle_cast({:enqueue, event}, %{queue: queue, demand: demand} = state) do
queue = :queue.in(event, queue)
dispatch_events(%{state | queue: queue}, demand, [])
end
def handle_demand(incoming_demand, %{demand: demand} = state) do
dispatch_events(state, demand + incoming_demand, [])
end
defp dispatch_events(%{queue: queue} = state, demand, events) when demand > 0 do
case :queue.out(queue) do
{{:value, event}, queue} ->
dispatch_events(%{state | queue: queue}, demand - 1, [event | events])
{:empty, _queue} ->
{:noreply, Enum.reverse(events), %{state | demand: demand}}
end
end
defp dispatch_events(state, 0, events) do
{:noreply, Enum.reverse(events), %{state | demand: 0}}
end
end
```
## Best Practices
### Code Organization
- Use contexts for domain boundaries
- Keep modules focused and small
- Prefer composition over inheritance
- Use protocols for polymorphism
### Error Handling
- Let it crash philosophy
- Use supervisors for fault tolerance
- Pattern match on {:ok, *} and {:error,*}
- Avoid defensive programming
### Performance
- Use ETS for read-heavy caching
- Leverage process isolation
- Use streams for large data
- Profile with :observer and :recon
### Testing
```elixir
defmodule MyApp.WorkerTest do
use ExUnit.Case, async: true
setup do
{:ok, pid} = MyApp.Worker.start_link(name: nil)
%{pid: pid}
end
describe "process/2" do
test "processes data successfully", %{pid: pid} do
assert {:ok, result} = MyApp.Worker.process(pid, %{data: "test"})
assert result.processed == true
end
test "handles errors gracefully", %{pid: pid} do
assert {:error, :invalid_data} = MyApp.Worker.process(pid, nil)
end
end
end
```
## Decision Framework
- Use GenServer for stateful processes
- Use Task for fire-and-forget async
- Use Agent for simple state
- Use GenStage for backpressure
- Use Phoenix Channels for real-time
- Use LiveView for interactive UIs
You write idiomatic, fault-tolerant Elixir code following OTP principles and Phoenix conventions.
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.