Elixir Cheatsheet

Maps and Structs

Use this Elixir reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Maps

user = %{name: "Ada", age: 36}
user.name          # dot access for atom keys, raises KeyError if missing
user[:age]         # bracket access, nil if missing
Map.get(user, :city, "unknown")
Map.fetch(user, :name)      # {:ok, "Ada"} or :error
Map.fetch!(user, :name)     # value or KeyError
Map.put(user, :city, "London")
Map.put_new(user, :age, 0)  # only if key absent
Map.delete(user, :age)
Map.has_key?(user, :name)
Map.keys(user); Map.values(user)
map_size(user)              # guard-safe

Maps are immutable. Update operations return new maps.

Atom Keys vs String Keys

%{name: "Ada"}          # atom keys — internal data
%{"name" => "Ada"}      # string keys — external/JSON/params data
%{"name" => name} = params    # match string keys with =>

%{key: value} is sugar for %{:key => value} and works only for atom keys. Never convert untrusted strings with String.to_atom/1 (atoms are not garbage collected); use String.to_existing_atom/1 or keep string keys.

Merging and Updating

Map.merge(%{a: 1, b: 2}, %{b: 3, c: 4})       # %{a: 1, b: 3, c: 4} — right wins
Map.merge(m1, m2, fn _k, v1, v2 -> v1 + v2 end)
Map.update(user, :age, 0, &(&1 + 1))          # default if absent, fun if present
Map.update!(user, :age, &(&1 + 1))            # raises if absent
Map.take(user, [:name, :age]); Map.drop(user, [:internal])

older = %{user | age: 37}     # update syntax: key MUST already exist (KeyError)

Use %{map | key: value} for updating known-present keys and Map.put/3 to add new ones.

Map Pattern Matching

%{name: name} = %{name: "Ada", age: 36}
%{status: :ok, value: value} = result

def promote(%{role: :member} = user), do: %{user | role: :admin}

A map pattern only needs to mention required keys; extra keys are allowed. An empty pattern %{} matches any map.

Iterating Maps

Enum.map(user, fn {k, v} -> {k, to_string(v)} end)   # maps enumerate as {k, v}
Map.new(user, fn {k, v} -> {k, transform(v)} end)    # map back into a map
Map.filter(user, fn {_k, v} -> v != nil end)
Map.reject(user, fn {_k, v} -> is_nil(v) end)
for {k, v} <- user, into: %{}, do: {k, v}

Maps are unordered — never rely on key order when enumerating.

Nested Updates

put_in(user.profile.name, "Ada")            # structs/atom keys
put_in(user, [:profile, :name], "Ada")      # Access path form
update_in(user.stats.logins, &(&1 + 1))
get_in(user, [:profile, :name])             # nil-safe traversal
get_in(data, ["users", Access.at(0), "email"])
pop_in(user, [:profile, :temp])             # {value, map_without_it}

get_in, put_in, update_in, and pop_in work through the Access behaviour; Access.at/1, Access.key/2, Access.all/0 handle lists and defaults.

Structs

defmodule User do
  defstruct name: "", admin: false
end

user = %User{name: "Ada"}
user.admin                    # false
%User{user | admin: true}
%User{name: name} = user      # matching asserts the struct type
user.__struct__               # User
struct(User, name: "Ada")     # build from keyword list/map, ignores unknown keys
struct!(User, name: "Ada")    # raises on unknown keys

A struct is a map with a fixed __struct__ field and a defined key set. Adding an undefined key (%User{nope: 1} or Map.put losing the guarantee) is a compile error / code smell. Structs do not implement the Access behaviour — use user.name, never user[:name].

Enforcing Keys

defmodule Invoice do
  @enforce_keys [:id, :total]
  defstruct [:id, :total, paid?: false]
end

Creating %Invoice{} without :id or :total raises ArgumentError at construction.

Functions Over Struct Shapes

def total(%Invoice{total: cents}), do: cents
def paid?(%Invoice{paid?: paid}), do: paid

def handle(%Invoice{} = inv), do: process(inv)   # type-check via match

Pattern matching in function heads documents and enforces the expected data shape. For struct-polymorphic behavior, use protocols (see Protocols and Behaviours).