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-safeMaps 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).