Elixir / Erlang: Idiomatic Efficiency Reference

← Back to skills

1. [Pattern Matching & Guards](#patterns) 2. [Pipe Operator & Transforms](#pipes) 3. [Processes & OTP](#otp) 4. [Error Handling](#errors) 5. [Collections & Enum](#collections) 6. [Structs & Protocols](#structs) 7. [Anti-patterns specific to Elixir/Erlang](#antipatterns)

Category: General & Miscellaneous
Repo: antigravity-awesome-skills
Path: skills/super-code/elixir/SKILL.md
Updated: 6/18/2026, 7:42:54 AM

AI Summary

1. [Pattern Matching & Guards](#patterns) 2. [Pipe Operator & Transforms](#pipes) 3. [Processes & OTP](#otp) 4. [Error Handling](#errors) 5. [Collections & Enum](#collections) 6. [Structs & Protocols](#structs) 7. [Anti-patterns specific to Elixir/Erlang](#antipatterns). It is useful for general automation, multi-purpose workflows, cross-disciplinary tasks, and utility skills. Source: antigravity-awesome-skills (skills/super-code/elixir/SKILL.md).

Elixir / Erlang: Idiomatic Efficiency Reference

Table of Contents

  1. Pattern Matching & Guards
  2. Pipe Operator & Transforms
  3. Processes & OTP
  4. Error Handling
  5. Collections & Enum
  6. Structs & Protocols
  7. Anti-patterns specific to Elixir/Erlang

1. Pattern Matching & Guards {#patterns}

# ❌ Extracting with Map.get then checking
value = Map.get(map, :key)
if value != nil do
  process(value)
end

# ✅ — pattern match directly
case map do
  %{key: value} -> process(value)
  _ -> :noop
end
# or with if:
if value = map[:key], do: process(value)
# ❌ Nested case for multiple conditions
case fetch_user(id) do
  {:ok, user} ->
    case validate(user) do
      {:ok, valid_user} -> save(valid_user)
      {:error, reason} -> {:error, reason}
    end
  {:error, reason} -> {:error, reason}
end

# ✅ — with clause
with {:ok, user} <- fetch_user(id),
     {:ok, valid_user} <- validate(user) do
  save(valid_user)
end
# ❌ if/else for known shapes
def area(shape) do
  if shape.type == :circle do
    :math.pi() * shape.radius * shape.radius
  else
    shape.width * shape.height
  end
end

# ✅ — multi-clause function with pattern match
def area(%{type: :circle, radius: r}), do: :math.pi() * r * r
def area(%{type: :rect, width: w, height: h}), do: w * h
# ❌ Checking type at runtime
def process(x) do
  if is_integer(x) and x > 0 do
    x * 2
  end
end

# ✅ — guard clause
def process(x) when is_integer(x) and x > 0, do: x * 2
def process(_), do: {:error, :invalid_input}

2. Pipe Operator & Transforms {#pipes}

# ❌ Nested function calls
String.trim(String.downcase(String.replace(input, ~r/\s+/, " ")))

# ✅
input
|> String.replace(~r/\s+/, " ")
|> String.downcase()
|> String.trim()
# ❌ Pipe into anonymous function awkwardly
data
|> (fn x -> x * 2 end).()

# ✅ — use then/1 or named function
data
|> then(&(&1 * 2))
# or better: extract a named function
data |> double()
# ❌ Single-step pipe (no gain in readability)
result = list |> Enum.count()

# ✅ — direct call for single operation
result = Enum.count(list)

Pipe when 2+ transforms. Direct call for single operation. First arg flows through pipe.


3. Processes & OTP {#otp}

# ❌ Raw spawn for stateful process
pid = spawn(fn -> loop(%{count: 0}) end)
send(pid, {:increment})

# ✅ — GenServer for stateful processes
defmodule Counter do
  use GenServer

  def start_link(init \\ 0), do: GenServer.start_link(__MODULE__, init)
  def increment(pid), do: GenServer.call(pid, :increment)

  @impl true
  def init(count), do: {:ok, count}

  @impl true
  def handle_call(:increment, _from, count), do: {:reply, count + 1, count + 1}
end
# ❌ Spawning without linking (orphan process on crash)
spawn(fn -> do_work() end)

# ✅ — Task for fire-and-forget with supervision
Task.start(fn -> do_work() end)
# or for awaitable result:
task = Task.async(fn -> do_work() end)
result = Task.await(task)
# ❌ Manual process registry
Process.register(self(), :my_worker)

# ✅ — use Registry or named GenServer
{:ok, _} = Registry.start_link(keys: :unique, name: MyRegistry)
GenServer.start_link(Worker, arg, name: {:via, Registry, {MyRegistry, :my_worker}})
# ❌ try/catch in GenServer (breaks supervision)
def handle_call(:work, _from, state) do
  try do
    result = risky_operation()
    {:reply, result, state}
  catch
    _ -> {:reply, :error, state}
  end
end

# ✅ — let it crash; supervisor restarts
def handle_call(:work, _from, state) do
  result = risky_operation()
  {:reply, result, state}
end

"Let it crash" — supervisors handle recovery. Don't defensively catch inside GenServers.


4. Error Handling {#errors}

# ❌ Raising for expected failures
def find_user(id) do
  case Repo.get(User, id) do
    nil -> raise "User not found"
    user -> user
  end
end

# ✅ — tagged tuples for expected outcomes
def find_user(id) do
  case Repo.get(User, id) do
    nil -> {:error, :not_found}
    user -> {:ok, user}
  end
end
# ❌ Ignoring error tuple
{:ok, result} = might_fail()  # crashes on {:error, _}

# ✅ — handle both cases
case might_fail() do
  {:ok, result} -> process(result)
  {:error, reason} -> Logger.error("Failed: #{inspect(reason)}")
end
# ❌ String errors
{:error, "something went wrong"}

# ✅ — atom or struct errors (matchable, cheap)
{:error, :timeout}
{:error, %ValidationError{field: :email, reason: :invalid_format}}
# ❌ Deep nesting of ok/error checks
case step1() do
  {:ok, a} ->
    case step2(a) do
      {:ok, b} ->
        case step3(b) do
          {:ok, c} -> {:ok, c}
          error -> error
        end
      error -> error
    end
  error -> error
end

# ✅
with {:ok, a} <- step1(),
     {:ok, b} <- step2(a),
     {:ok, c} <- step3(b) do
  {:ok, c}
else
  {:error, reason} -> {:error, reason}
end

5. Collections & Enum {#collections}

# ❌ Multiple passes when one suffices
items
|> Enum.filter(&(&1.active))
|> Enum.map(&(&1.name))

# ✅ — for comprehension when filter + transform
for %{active: true, name: name} <- items, do: name
# ❌ Enum.count for empty check (traverses whole list)
if Enum.count(list) == 0, do: :empty

# ✅
if Enum.empty?(list), do: :empty
# or pattern match:
case list do
  [] -> :empty
  _ -> :has_items
end
# ❌ Building map with Enum.reduce when Map.new works
Enum.reduce(users, %{}, fn user, acc -> Map.put(acc, user.id, user) end)

# ✅
Map.new(users, &{&1.id, &1})
# ❌ Enum on large dataset (eager — builds intermediate lists)
huge_list
|> Enum.map(&transform/1)
|> Enum.filter(&valid?/1)
|> Enum.take(10)

# ✅ — Stream for lazy evaluation
huge_list
|> Stream.map(&transform/1)
|> Stream.filter(&valid?/1)
|> Enum.take(10)

Use Stream when chaining transforms on large/infinite collections. Enum for small or final step.


6. Structs & Protocols {#structs}

# ❌ Plain map for domain entities
user = %{name: "Alice", email: "a@b.com", age: 30}
# typo in key goes unnoticed: user.emaail

# ✅ — struct enforces keys
defmodule User do
  @enforce_keys [:name, :email]
  defstruct [:name, :email, age: 0]
end
user = %User{name: "Alice", email: "a@b.com"}
# ❌ Protocol with only one implementation (over-abstraction)
defprotocol Renderable do
  def render(data)
end
defimpl Renderable, for: HtmlPage do ... end

# ✅ — just a function until you need polymorphism
def render(%HtmlPage{} = page), do: ...
# ❌ Updating nested struct manually
updated = %{user | address: %{user.address | city: "NYC"}}

# ✅
updated = put_in(user.address.city, "NYC")
# or Kernel.update_in/3 for transforms

7. Anti-patterns specific to Elixir/Erlang {#antipatterns}

Anti-patternPreferred
spawn without link/monitorTask.start_link or GenServer
try/catch inside GenServerlet it crash; supervisor restarts
String error reasonsatom or struct errors
Enum.count(x) == 0Enum.empty?(x) or match?([], x)
Mutable-style accumulatorEnum.reduce / recursion
if/else chain on data shapemulti-clause function + pattern match
Nested case for ok/errorwith expression
IO.inspect left in prodLogger with levels
Single-step pipedirect function call
Enum on huge/infinite dataStream
Raw PID passingnamed processes / Registry
Boolean returns for success/fail{:ok, val} / {:error, reason} tuples
length(list) > 0 (O(n))pattern match `[_
Shared mutable state via ETS without wrapperGenServer or Agent as access layer

Limitations

  • These are language-specific guidelines and do not cover overall architectural decisions.
  • Over-compression might reduce readability; apply judgement.

Related skills