Maps and Keyword Lists
Elixir's two key-value structures -- when to use a map versus a keyword list, and the operations each supports.
3 min read
Elixir has two distinct key-value data structures — maps and keyword lists — and picking the right one depends on whether you need fast lookups or ordered, repeatable keys.
Maps: the general-purpose key-value store
user = %{name: "Ada", age: 30, role: "admin"}Reading values:
user.name
# => "Ada"
user[:age]
# => 30
Map.get(user, :email, "not set")
# => "not set" -- default when key is missingBecause maps are the default choice for structured data, updating them comes up constantly. Elixir gives you a few ways to do it, all of which return a new map (remember, everything is immutable):
Map.put(user, :age, 31)
# => %{name: "Ada", age: 31, role: "admin"}
%{user | age: 31}
# => %{name: "Ada", age: 31, role: "admin"}The %{map | key: value} update syntax is faster and more common, but it only works for keys that already exist in the map — it will raise if you try to use it to add a brand-new key. For that, use Map.put/3 instead.
Map.merge(user, %{role: "owner", active: true})
# => %{name: "Ada", age: 30, role: "owner", active: true}Map.merge/2 combines two maps, with the second map's keys winning on conflicts.
Iterating a map
for {key, value} <- user do
IO.puts("#{key}: #{value}")
endMaps implement the Enumerable protocol, so every Enum function — Enum.map/2, Enum.filter/2, Enum.reduce/3 — works on them too, iterating as {key, value} tuples.
Keyword lists: ordered, allow duplicates
A keyword list is a list of two-element tuples where each key is an atom, with special syntax sugar:
opts = [color: "red", size: :large]
# actually: [{:color, "red"}, {:size, :large}]Unlike maps, keyword lists preserve order and allow the same key more than once:
opts = [error: "invalid", error: "required"]
Keyword.get_values(opts, :error)
# => ["invalid", "required"]That combination — ordered, duplicate-friendly, atom-only keys — makes keyword lists the natural fit for one specific job: options passed to a function.
String.pad_leading("5", 3, "0")
# a made-up API using options, following the convention:
def connect(host, opts \\ []) do
timeout = Keyword.get(opts, :timeout, 5000)
retries = Keyword.get(opts, :retries, 3)
# ...
end
connect("example.com", timeout: 10_000, retries: 5)That last call is why keyword lists feel almost invisible in everyday Elixir: timeout: 10_000, retries: 5 as the last argument to a function is a keyword list, using Elixir's special syntax that lets you drop the brackets and braces when it's the final argument.
Maps vs. keyword lists, side by side
| | Map | Keyword list | |---|---|---| | Key types | Any type | Atoms only | | Duplicate keys | No | Yes | | Order preserved | No | Yes | | Lookup speed | Fast (hashed) | Slow (linear scan) | | Typical use | General data, structs | Function options, small configs |
The rule of thumb used throughout the Elixir ecosystem: reach for a map for general-purpose data of any real size, and a keyword list specifically for optional arguments passed into a function call — which is exactly the convention you'll see in Phoenix, Ecto, and virtually every other Elixir library later in this course.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.