Installation and IEx
Setting up Elixir is straightforward, but the choice of installation method matters more than people admit. If you only ever work on one Elixir project, Homebrew is fine. The moment you have two projects on different versions, or you join a team that pins versions in .tool-versions, you'll wish you'd started with asdf. Save yourself the migration.
Installing With asdf
asdf is a version manager that handles Elixir, Erlang, Node, Ruby, and dozens of other runtimes through plugins. It reads a .tool-versions file in any project to pick the right version automatically, which is how every serious Elixir team works.
# install asdf itself (macOS)
brew install asdf
# add the plugins
asdf plugin add erlang
asdf plugin add elixir
# install a specific version
asdf install erlang 27.1.2
asdf install elixir 1.17.3-otp-27
# set it globally
asdf global erlang 27.1.2
asdf global elixir 1.17.3-otp-27
Always pair an Elixir version with the matching OTP major version. 1.17.3-otp-27 means Elixir 1.17.3 compiled against OTP 27. Mixing OTP versions across machines on a team causes weird crashes that take hours to debug.
In a project, drop a .tool-versions file at the root:
erlang 27.1.2
elixir 1.17.3-otp-27
asdf will pick those up the moment you cd into the directory.
Installing With Homebrew
For quick experiments:
brew install elixir
This pulls in the latest Erlang and Elixir from Homebrew's repos. Fine for tinkering. Don't use it for anything you intend to ship, because you can't pin the version and a brew upgrade will silently change it under you.
Verifying the Install
elixir --version
You should see something like:
Erlang/OTP 27 [erts-15.1.2] [source] [64-bit] [smp:10:10]
Elixir 1.17.3 (compiled with Erlang/OTP 27)
If the Erlang line is missing or shows a different OTP version than you expected, your asdf shims aren't on the PATH. Add . $(brew --prefix asdf)/libexec/asdf.sh to your shell rc file.
The IEx Shell
iex is the Elixir REPL, and it's where you'll spend a surprising amount of time. Unlike Python's REPL, IEx is a full-featured environment: you can attach to running production nodes with it, hot-reload code, inspect process state, and pipe expressions across multiple lines.
iex
Erlang/OTP 27 [erts-15.1.2] [source] [64-bit] [smp:10:10]
Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
A few things to know up front:
Ctrl+Conce shows a menu. Pressato abort, or hitCtrl+Ctwice to exit fast.h Module.functionshows documentation. Tryh Enum.map. This is real, browsable docs from inside the shell.i valueinspects any value's type and metadata.recompile()reloads modules from your project after you've edited a file.
iex(1)> h Enum.map
def map(enumerable, fun)
@spec map(t(), (element() -> any())) :: list()
Returns a list where each element is the result of invoking fun on each
corresponding element of enumerable.
...
The built-in help is one of those quality-of-life features you start to miss in other languages.
Mix: The Build Tool
mix is Elixir's project tool — package manager, test runner, task system, and build orchestrator rolled into one. Every Elixir project has a mix.exs file at its root.
mix new my_app
This generates:
my_app/
├── .formatter.exs
├── .gitignore
├── README.md
├── lib/
│ └── my_app.ex
├── mix.exs
└── test/
├── my_app_test.exs
└── test_helper.exs
For an OTP application with a supervision tree (most real projects):
mix new my_app --sup
The --sup flag adds an Application module and starts a supervisor. You'll want this any time you're building a service rather than a library.
Project Structure
Open mix.exs and you'll see two functions: project/0 and application/0.
defmodule MyApp.MixProject do
use Mix.Project
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger],
mod: {MyApp.Application, []}
]
end
defp deps do
[
# {:phoenix, "~> 1.7.0"},
# {:ecto_sql, "~> 3.10"}
]
end
end
project/0 is metadata for Mix itself. application/0 is what gets baked into the OTP application — it controls which modules start when your app boots.
Add a dependency in deps/0, then run:
mix deps.get
Mix downloads packages from Hex (the package registry) and stores them in deps/. Compilation goes into _build/. Both should be in .gitignore (the generator handles this).
Running Tests
mix test
mix test runs every *_test.exs file under test/. ExUnit, the built-in test framework, is fast and pleasant. Run a single file:
mix test test/my_app_test.exs
Run a single test by line number:
mix test test/my_app_test.exs:14
Run only tests tagged :integration:
mix test --only integration
Most teams set up mix test --cover and mix format --check-formatted in CI. Don't skip the formatter — Elixir's official formatter is opinionated and avoids the bikeshed wars you'd otherwise have on PR review.
The .iex.exs File
This is the trick that makes IEx genuinely useful for daily work. If you put a file called .iex.exs at your project root, IEx evaluates it on startup. You can pre-load aliases, helper functions, sample data, anything.
# .iex.exs
import Ecto.Query
alias MyApp.{Repo, Accounts, Accounts.User}
IO.puts("loaded: Repo, Accounts, User, Ecto.Query")
defmodule H do
def user(id), do: Repo.get(User, id)
def users, do: Repo.all(User) |> Enum.take(20)
end
Now when you start iex -S mix, you get:
loaded: Repo, Accounts, User, Ecto.Query
iex(1)> H.user(42)
%MyApp.Accounts.User{id: 42, email: "ada@example.com", ...}
Teams often commit a .iex.exs.example and let each developer copy it to a personal .iex.exs they don't commit. The personal one can have throwaway debugging helpers nobody else needs to see.
Common Pitfalls
Mixing OTP versions across team members. Pin both Erlang and Elixir in .tool-versions. Don't trust "it works on my machine."
Forgetting iex -S mix. Plain iex won't load your project. The -S mix flag tells IEx to start your project's application first, which means your modules and dependencies are available.
Editing files but seeing old code. IEx caches loaded modules. Run recompile() after edits, or restart IEx. Better, use mix test --listen-on-stdin for an auto-running test loop.
Ignoring the formatter. mix format is not optional. CI should fail without it. Editors can run it on save. Code review without formatter wars is a quiet luxury.
Treating .iex.exs as a config file. It's evaluated as Elixir code on startup. Don't put secrets there if you commit it. Don't put expensive setup either, because it runs every time.
Key Takeaways
- Use
asdffor version management from day one. Homebrew is fine for tinkering, painful for teams. mix new --supfor services,mix newfor libraries.- IEx is a real tool, not a toy. Learn
h,i, andrecompile(). - Pin versions in
.tool-versionsand commit it. - A project-level
.iex.exssaves hours of re-typing aliases and queries. mix formatis non-negotiable. Run it in CI.