Erlang / Elixir Mastery (Senior → Principal)
Operate
- Start by confirming: language choice (Erlang or Elixir), OTP version, cluster topology, persistence strategy, workload shape, availability goals, and the definition of done.
- Model the system around processes, supervision, message flow, and failure isolation before discussing frameworks.
- Prefer clear OTP ownership and supervision over custom concurrency control.
- Optimize for operability: observability, restart semantics, mailbox health, and degraded-mode behavior are part of the design.
The goal is not to show off the actor model. The goal is a BEAM system that fails in contained ways, recovers predictably, and remains easy to reason about during incidents.
Default Standards
- Use OTP behaviors (
GenServer, Supervisor, DynamicSupervisor, GenStage when justified) instead of ad-hoc process orchestration.
- Keep Phoenix or HTTP layers thin; move policy and orchestration into testable modules.
- Define message contracts and state transitions explicitly.
- Treat mailbox growth, process cardinality, and backpressure as primary production risks.
- Prefer supervision trees and retries at the right layer over defensive rescue blocks everywhere.
- Make timeout, retry, and circuit breaker behavior explicit for outbound dependencies.
- Use telemetry and structured logging consistently.
“Bad vs Good” (common production pitfalls)
# ❌ BAD: using a GenServer as a catch-all service locator.
def handle_call({:do_everything, payload}, _from, state) do
result = BigBallOfMud.run(payload, state)
{:reply, result, state}
end
# ✅ GOOD: keep the process focused and delegate domain behavior.
def handle_call({:create_user, attrs}, _from, state) do
case Users.create(attrs, state.deps) do
{:ok, user} -> {:reply, {:ok, user}, state}
{:error, reason} -> {:reply, {:error, reason}, state}
end
end
# ❌ BAD: unbounded Task spawning in a request path.
Enum.each(events, fn event ->
Task.start(fn -> publish(event) end)
end)
# ✅ GOOD: use supervised, bounded concurrency.
Task.Supervisor.async_stream_nolink(MyApp.TaskSupervisor, events, &publish/1, max_concurrency: 8, timeout: 5_000)
|> Stream.run()
# ❌ BAD: catch-all rescue hides operational signals.
try do
external_call()
rescue
_ -> :ok
end
# ✅ GOOD: classify the failure and surface it.
case external_call() do
{:ok, result} -> {:ok, result}
{:error, :timeout} -> {:error, :dependency_timeout}
{:error, reason} -> {:error, {:dependency_failed, reason}}
end
Workflow (Feature / Refactor / Bug)
- Reproduce the issue or encode the expected behavior in tests.
- Decide process ownership, supervision strategy, and message boundaries.
- Define failure semantics: restart, retry, drop, dead-letter, or escalate.
- Implement the smallest end-to-end slice.
- Validate mailbox behavior, process counts, timeout paths, and telemetry.
- Review cluster behavior, deploy safety, and rollback readiness.
Validation Commands
- Run
mix format --check-formatted.
- Run
mix test.
- Run
mix credo --strict if Credo is used.
- Run
mix dialyzer for type/spec analysis when configured.
- Run
mix test --trace during debugging.
- Run
mix phx.routes and endpoint smoke checks for Phoenix applications.
- Run release smoke tests if the application ships as an OTP release.
OTP and Concurrency Guardrails
- Every long-lived process should have a clear owner and supervision policy.
- Avoid turning one GenServer into a bottleneck for unrelated responsibilities.
- Bound concurrency for fan-out work; use supervised tasks or queues.
- Watch mailbox growth, reduction spikes, and scheduler imbalance.
- Prefer message passing over shared mutable escape hatches such as ETS misuse.
- Use ETS intentionally: great for read-heavy shared data, dangerous as hidden global state.
Service and API Defaults
- Validate input at the boundary before it reaches domain logic.
- Map domain errors consistently to HTTP, gRPC, or queue semantics.
- Use idempotency keys for retried commands with side effects.
- Set explicit client timeouts and pool limits for outbound HTTP/database calls.
- Do not leak stack traces or internal exception details to clients.
Reliability, Distributed Systems, and Operations
- Design supervision trees around blast radius, not just code organization.
- Decide what must restart together and what must fail independently.
- Be explicit about node discovery, cookie management, and cluster partitions.
- Treat netsplits, mailbox buildup, and dependency slowness as first-class failure modes.
- Expose health checks, readiness, telemetry, and trace correlation for incident response.
Security Checklist (Minimum)
- Keep secrets out of logs, process state dumps, and crash reports.
- Validate payload size and shape for all external inputs.
- Use least-privilege credentials and role separation.
- Harden admin endpoints, LiveDashboard, and remote shell access.
- Audit distributed node trust boundaries before enabling clustering across networks.
Decision Heuristics
Choose Erlang/Elixir when:
- the workload is highly concurrent and failure isolation matters
- soft real-time behavior and uptime are core requirements
- supervision and message passing are natural fits for the domain
- the team benefits from OTP and BEAM operational strengths
Prefer another backend stack when:
- the problem is simple CRUD with no resilience or concurrency pressure
- ecosystem maturity for a required niche library is missing
- the team cannot support BEAM operational knowledge yet
References
- OTP design and supervision boundaries: references/otp-design.md
- Phoenix and service boundaries: references/phoenix-and-boundaries.md
- Distributed systems and operations: references/distributed-systems-and-operations.md
- Telemetry and observability: references/telemetry-and-observability.md
- Incident runbooks: references/incident-runbooks.md
1---2name: erlang-elixir-principal-engineer3description: Principal/Senior-level Erlang and Elixir playbook for highly available backend systems, OTP design, actor-model concurrency, reliability, observability, and production operations. Use when: building or reviewing Elixir or Erlang services, designing OTP supervision trees, handling high-concurrency workloads, debugging distributed failures, improving resilience, or preparing BEAM systems for production.4---56# Erlang / Elixir Mastery (Senior → Principal)78## Operate910- Start by confirming: language choice (Erlang or Elixir), OTP version, cluster topology, persistence strategy, workload shape, availability goals, and the definition of done.11- Model the system around processes, supervision, message flow, and failure isolation before discussing frameworks.12- Prefer clear OTP ownership and supervision over custom concurrency control.13- Optimize for operability: observability, restart semantics, mailbox health, and degraded-mode behavior are part of the design.1415> The goal is not to show off the actor model. The goal is a BEAM system that fails in contained ways, recovers predictably, and remains easy to reason about during incidents.1617## Default Standards1819- Use OTP behaviors (`GenServer`, `Supervisor`, `DynamicSupervisor`, `GenStage` when justified) instead of ad-hoc process orchestration.20- Keep Phoenix or HTTP layers thin; move policy and orchestration into testable modules.21- Define message contracts and state transitions explicitly.22- Treat mailbox growth, process cardinality, and backpressure as primary production risks.23- Prefer supervision trees and retries at the right layer over defensive rescue blocks everywhere.24- Make timeout, retry, and circuit breaker behavior explicit for outbound dependencies.25- Use telemetry and structured logging consistently.2627## “Bad vs Good” (common production pitfalls)2829```elixir30# ❌ BAD: using a GenServer as a catch-all service locator.31def handle_call({:do_everything, payload}, _from, state) do32 result = BigBallOfMud.run(payload, state)33 {:reply, result, state}34end3536# ✅ GOOD: keep the process focused and delegate domain behavior.37def handle_call({:create_user, attrs}, _from, state) do38 case Users.create(attrs, state.deps) do39 {:ok, user} -> {:reply, {:ok, user}, state}40 {:error, reason} -> {:reply, {:error, reason}, state}41 end42end43```4445```elixir46# ❌ BAD: unbounded Task spawning in a request path.47Enum.each(events, fn event ->48 Task.start(fn -> publish(event) end)49end)5051# ✅ GOOD: use supervised, bounded concurrency.52Task.Supervisor.async_stream_nolink(MyApp.TaskSupervisor, events, &publish/1, max_concurrency: 8, timeout: 5_000)53|> Stream.run()54```5556```elixir57# ❌ BAD: catch-all rescue hides operational signals.58try do59 external_call()60rescue61 _ -> :ok62end6364# ✅ GOOD: classify the failure and surface it.65case external_call() do66 {:ok, result} -> {:ok, result}67 {:error, :timeout} -> {:error, :dependency_timeout}68 {:error, reason} -> {:error, {:dependency_failed, reason}}69end70```7172## Workflow (Feature / Refactor / Bug)73741. Reproduce the issue or encode the expected behavior in tests.752. Decide process ownership, supervision strategy, and message boundaries.763. Define failure semantics: restart, retry, drop, dead-letter, or escalate.774. Implement the smallest end-to-end slice.785. Validate mailbox behavior, process counts, timeout paths, and telemetry.796. Review cluster behavior, deploy safety, and rollback readiness.8081## Validation Commands8283- Run `mix format --check-formatted`.84- Run `mix test`.85- Run `mix credo --strict` if Credo is used.86- Run `mix dialyzer` for type/spec analysis when configured.87- Run `mix test --trace` during debugging.88- Run `mix phx.routes` and endpoint smoke checks for Phoenix applications.89- Run release smoke tests if the application ships as an OTP release.9091## OTP and Concurrency Guardrails9293- Every long-lived process should have a clear owner and supervision policy.94- Avoid turning one GenServer into a bottleneck for unrelated responsibilities.95- Bound concurrency for fan-out work; use supervised tasks or queues.96- Watch mailbox growth, reduction spikes, and scheduler imbalance.97- Prefer message passing over shared mutable escape hatches such as ETS misuse.98- Use ETS intentionally: great for read-heavy shared data, dangerous as hidden global state.99100## Service and API Defaults101102- Validate input at the boundary before it reaches domain logic.103- Map domain errors consistently to HTTP, gRPC, or queue semantics.104- Use idempotency keys for retried commands with side effects.105- Set explicit client timeouts and pool limits for outbound HTTP/database calls.106- Do not leak stack traces or internal exception details to clients.107108## Reliability, Distributed Systems, and Operations109110- Design supervision trees around blast radius, not just code organization.111- Decide what must restart together and what must fail independently.112- Be explicit about node discovery, cookie management, and cluster partitions.113- Treat netsplits, mailbox buildup, and dependency slowness as first-class failure modes.114- Expose health checks, readiness, telemetry, and trace correlation for incident response.115116## Security Checklist (Minimum)117118- Keep secrets out of logs, process state dumps, and crash reports.119- Validate payload size and shape for all external inputs.120- Use least-privilege credentials and role separation.121- Harden admin endpoints, LiveDashboard, and remote shell access.122- Audit distributed node trust boundaries before enabling clustering across networks.123124## Decision Heuristics125126```text127Choose Erlang/Elixir when:128- the workload is highly concurrent and failure isolation matters129- soft real-time behavior and uptime are core requirements130- supervision and message passing are natural fits for the domain131- the team benefits from OTP and BEAM operational strengths132133Prefer another backend stack when:134- the problem is simple CRUD with no resilience or concurrency pressure135- ecosystem maturity for a required niche library is missing136- the team cannot support BEAM operational knowledge yet137```138139## References140141- OTP design and supervision boundaries: [references/otp-design.md](references/otp-design.md)142- Phoenix and service boundaries: [references/phoenix-and-boundaries.md](references/phoenix-and-boundaries.md)143- Distributed systems and operations: [references/distributed-systems-and-operations.md](references/distributed-systems-and-operations.md)144- Telemetry and observability: [references/telemetry-and-observability.md](references/telemetry-and-observability.md)145- Incident runbooks: [references/incident-runbooks.md](references/incident-runbooks.md)