Overview
Elixir is a dynamic, functional programming language designed for building scalable and maintainable applications. It leverages the Erlang VM (BEAM), known for running low-latency, distributed, and fault-tolerant systems. Elixir provides productive tooling and an extensible design, with first-class support for metaprogramming via macros, polymorphism via protocols, and concurrent programming via lightweight processes.
The language was created by Jose Valim and first appeared in 2011. Elixir builds on top of decades of Erlang/OTP battle-tested infrastructure while providing a modern syntax, comprehensive standard library, and excellent developer experience. It powers real-time systems, web applications via the Phoenix framework, embedded systems via Nerves, and data processing pipelines via Broadway and Flow.
Installation
macOS
# Using Homebrew
brew install elixir
# Verify installation
elixir --version
iex --version
Ubuntu/Debian
# Add Erlang Solutions repo
wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
sudo dpkg -i erlang-solutions_2.0_all.deb
sudo apt-get update
# Install Erlang and Elixir
sudo apt-get install esl-erlang elixir
Using asdf Version Manager
# Install asdf plugins
asdf plugin add erlang
asdf plugin add elixir
# Install specific versions
asdf install erlang 26.2
asdf install elixir 1.16.1-otp-26
# Set global versions
asdf global erlang 26.2
asdf global elixir 1.16.1-otp-26
Docker
docker run -it --rm elixir:1.16-slim iex
Core Language Features
Data Types
| Type | Example | Description |
|---|
| Integer | 42, 0xFF, 0b1010 | Arbitrary precision integers |
| Float | 3.14, 1.0e-10 | 64-bit double precision |
| Atom | :ok, :error, true | Constants whose name is their value |
| String | "hello" | UTF-8 encoded binaries |
| List | [1, 2, 3] | Linked lists |
| Tuple | {:ok, "value"} | Fixed-size containers |
| Map | %{key: "value"} | Key-value store |
| Keyword List | [name: "Elixir"] | Ordered list of atom-keyed tuples |
| Range | 1..10 | Inclusive range |
| Binary | <<1, 2, 3>> | Sequence of bytes |
Pattern Matching
# Basic pattern matching
{:ok, result} = {:ok, 42}
[head | tail] = [1, 2, 3, 4]
# Function clause matching
defmodule Math do
def factorial(0), do: 1
def factorial(n) when n > 0, do: n * factorial(n - 1)
end
# Case expression
case HTTPClient.get(url) do
{:ok, %{status: 200, body: body}} -> parse(body)
{:ok, %{status: 404}} -> :not_found
{:error, reason} -> {:error, reason}
end
# With expression for chaining matches
with {:ok, user} <- fetch_user(id),
{:ok, profile} <- fetch_profile(user),
{:ok, avatar} <- fetch_avatar(profile) do
{:ok, %{user: user, profile: profile, avatar: avatar}}
else
{:error, reason} -> {:error, reason}
end
Pipe Operator
# Transform data through a pipeline
"Hello, World!"
|> String.downcase()
|> String.split(", ")
|> Enum.map(&String.capitalize/1)
|> Enum.join(" - ")
# => "Hello - World!"
Modules and Functions
defmodule MyApp.User do
@moduledoc "User management module"
defstruct [:name, :email, age: 0]
@doc "Creates a new user"
def new(name, email) do
%__MODULE__{name: name, email: email}
end
# Private function
defp validate_email(email) do
String.contains?(email, "@")
end
# Default arguments
def greet(user, greeting \\ "Hello") do
"#{greeting}, #{user.name}!"
end
# Guard clauses
def set_age(user, age) when is_integer(age) and age >= 0 do
%{user | age: age}
end
end
Enum and Stream
Common Enum Functions
| Function | Example | Description |
|---|
map/2 | Enum.map([1,2,3], &(&1 * 2)) | Transform each element |
filter/2 | Enum.filter(list, &(&1 > 2)) | Keep matching elements |
reduce/3 | Enum.reduce(list, 0, &+/2) | Accumulate to single value |
find/2 | Enum.find(list, &(&1 > 5)) | First matching element |
sort/2 | Enum.sort(list, :desc) | Sort elements |
group_by/2 | Enum.group_by(users, & &1.role) | Group by function result |
chunk_every/2 | Enum.chunk_every(list, 3) | Split into chunks |
flat_map/2 | Enum.flat_map(list, &expand/1) | Map and flatten |
zip/2 | Enum.zip(keys, values) | Pair elements together |
uniq_by/2 | Enum.uniq_by(users, & &1.email) | Unique by function |
Lazy Streams
# Streams are lazy and composable
1..1_000_000
|> Stream.filter(&(rem(&1, 2) == 0))
|> Stream.map(&(&1 * 3))
|> Stream.take(10)
|> Enum.to_list()
# Infinite streams
Stream.iterate(0, &(&1 + 1))
|> Stream.filter(&(rem(&1, 3) == 0))
|> Enum.take(5)
# => [0, 3, 6, 9, 12]
# File streaming (memory efficient)
File.stream!("large_file.csv")
|> Stream.map(&String.trim/1)
|> Stream.reject(&(&1 == ""))
|> Enum.take(100)
Concurrency with Processes
Spawning Processes
# Spawn a process
pid = spawn(fn -> IO.puts("Hello from process!") end)
# Spawn with link (crash propagation)
pid = spawn_link(fn -> do_work() end)
# Send and receive messages
send(self(), {:hello, "world"})
receive do
{:hello, msg} -> IO.puts("Got: #{msg}")
after
5000 -> IO.puts("Timeout!")
end
GenServer
defmodule MyApp.Counter do
use GenServer
# Client API
def start_link(initial \\ 0) do
GenServer.start_link(__MODULE__, initial, name: __MODULE__)
end
def increment, do: GenServer.cast(__MODULE__, :increment)
def get_count, do: GenServer.call(__MODULE__, :get)
# Server callbacks
@impl true
def init(count), do: {:ok, count}
@impl true
def handle_cast(:increment, count), do: {:noreply, count + 1}
@impl true
def handle_call(:get, _from, count), do: {:reply, count, count}
end
Task and Agent
# Task for async work
task = Task.async(fn -> expensive_computation() end)
result = Task.await(task, 10_000)
# Multiple concurrent tasks
tasks = Enum.map(urls, fn url ->
Task.async(fn -> HTTPClient.get(url) end)
end)
results = Task.await_many(tasks, 30_000)
# Agent for simple state
{:ok, pid} = Agent.start_link(fn -> %{} end)
Agent.update(pid, &Map.put(&1, :key, "value"))
Agent.get(pid, &Map.get(&1, :key))
Common Commands
| Command | Description |
|---|
mix new my_app | Create new project |
mix new my_app --sup | Create project with supervision tree |
mix deps.get | Fetch dependencies |
mix deps.update --all | Update all deps |
mix compile | Compile project |
mix test | Run test suite |
mix test --cover | Run tests with coverage |
mix format | Format code |
mix format --check-formatted | Check formatting (CI) |
mix hex.outdated | Check for outdated deps |
mix docs | Generate documentation |
mix release | Build release |
mix.exs Configuration
defmodule MyApp.MixProject do
use Mix.Project
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.16",
start_permanent: Mix.env() == :prod,
deps: deps(),
aliases: aliases()
]
end
def application do
[
extra_applications: [:logger],
mod: {MyApp.Application, []}
]
end
defp deps do
[
{:phoenix, "~> 1.7"},
{:ecto_sql, "~> 3.11"},
{:jason, "~> 1.4"},
{:ex_doc, "~> 0.31", only: :dev, runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
]
end
defp aliases do
[
setup: ["deps.get", "ecto.setup"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
]
end
end
Configuration
Runtime Configuration
# config/config.exs
import Config
config :my_app,
ecto_repos: [MyApp.Repo]
config :my_app, MyApp.Repo,
database: "my_app_dev",
hostname: "localhost"
# config/runtime.exs (runtime config)
import Config
if config_env() == :prod do
config :my_app, MyApp.Repo,
url: System.get_env("DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
end
Advanced Usage
Behaviours and Protocols
# Define a behaviour
defmodule MyApp.Cache do
@callback get(key :: String.t()) :: {:ok, term()} | :miss
@callback put(key :: String.t(), value :: term(), ttl :: integer()) :: :ok
end
# Implement a protocol
defprotocol MyApp.Renderable do
@doc "Renders a data structure to string"
def render(data)
end
defimpl MyApp.Renderable, for: Map do
def render(map), do: Jason.encode!(map)
end
defmodule MyApp.Retry do
defmacro with_retry(opts \\ [], do: block) do
max = Keyword.get(opts, :max_attempts, 3)
quote do
Enum.reduce_while(1..unquote(max), nil, fn attempt, _acc ->
try do
{:halt, unquote(block)}
rescue
e -> if attempt == unquote(max), do: {:halt, {:error, e}}, else: {:cont, nil}
end
end)
end
end
end
Troubleshooting
| Problem | Solution |
|---|
(UndefinedFunctionError) | Ensure module is compiled; check function arity |
(MatchError) | Pattern doesn’t match value; add catch-all clause |
(Protocol.UndefinedError) | Implement protocol for the given type |
| Process crash cascading | Use Supervisors with proper restart strategies |
| Memory growing unbounded | Check for process mailbox buildup; use Process.info/2 |
| Slow compilation | Check for circular module dependencies |
mix deps.get failures | Clear _build and deps dirs, retry |
| OTP version mismatch | Ensure Erlang/OTP version matches elixir_make requirements |