100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Maps in Elixir

Understand Elixir's key-value data structure, how to build, update, and pattern-match against maps, and when to reach for structs instead.

Data & CollectionsBeginner10 min readJul 10, 2026
Analogies

Maps in Elixir

A map is Elixir's general-purpose key-value store, written %{key => value} or, for atom keys, the shorthand %{key: value}. Unlike keyword lists, map keys are unique — inserting a duplicate key overwrites the existing value — and keys can be any term: atoms, strings, tuples, or even other maps. Maps give average O(1) lookup, insertion, and update via an efficient hashed internal representation, which is why they are the default choice in Elixir for representing structured data such as a parsed JSON payload, configuration, or a user record.

🏏

Cricket analogy: A map is like a player's career stats card keyed by format — %{test: 52.3, odi: 45.1, t20: 38.9} — where you look up the T20 average directly rather than scanning a full stat sheet, the way ESPNcricinfo profiles Virat Kohli.

Creating and Updating Maps

You build a map literally with %{a: 1, b: 2}, or from a list of pairs with Map.new/1. Reading a value uses map.key for atom keys or map[key] for any key type, both returning nil if missing, or Map.fetch/2 which returns {:ok, value} / :error. Updating an existing key uses the update syntax %{map | key: value}, which is efficient because it reuses the map's internal structure and only fails if the key does not already exist — for adding brand-new keys you use Map.put/3 instead.

🏏

Cricket analogy: %{scorecard | runs: 245} mirrors updating a live scoreboard's runs total mid-innings without rebuilding the whole card, the way a stadium screen updates after every boundary.

Pattern Matching and Nested Access

Maps pattern match partially: %{name: name} = user matches as long as user has at least a :name key, ignoring any other keys present, which makes function heads like def greet(%{name: name}), do: ... a concise way to destructure only the fields you care about. For deeply nested data, get_in/2, put_in/3, and update_in/3 with access paths like [:user, :address, :city] let you read or update nested maps without manually threading through each level, which is far less error-prone than chained pattern matches.

🏏

Cricket analogy: Matching %{name: name} against a full player record mirrors a commentator caring only about the batter's name field from a much larger stats object mid-broadcast.

Maps vs. Structs

A struct, defined with defstruct inside a module, is a specialized map with a fixed set of keys, a compile-time-checked __struct__ field, and no default Enumerable or Access protocol implementation unless you opt in — this trade-off protects you from typos in key names and from accidentally treating structured domain data as a generic bag of keys. Choose a plain map for loosely shaped or dynamic data (decoded JSON, config from a file), and choose a struct when you have a fixed, well-known shape like %User{name: ..., email: ...} that benefits from compile-time guarantees and pattern-matching on the struct name itself.

🏏

Cricket analogy: A struct %Player{name: ..., role: ...} is like an official ICC player registration form with fixed fields, whereas a raw map is like a scribbled scouting note that can carry any extra observation.

elixir
user = %{name: "Ada", age: 30, roles: [:admin]}

user.name                          # "Ada"
Map.get(user, :email, "none")       # "none" - default when missing
Map.fetch(user, :age)               # {:ok, 30}

updated = %{user | age: 31}         # key must already exist
with_email = Map.put(user, :email, "ada@example.com")

%{name: name, roles: roles} = user
IO.puts("#{name} has roles: #{inspect(roles)}")

nested = %{profile: %{address: %{city: "Lagos"}}}
get_in(nested, [:profile, :address, :city])   # "Lagos"
put_in(nested, [:profile, :address, :city], "Nairobi")

The update syntax %{map | key: value} raises a KeyError if key does not already exist in map — it can only update, never add. Use Map.put/3 or Map.put_new/3 when the key might be missing.

Map key lookup, insertion, and deletion are average-case O(1) thanks to Elixir's persistent hash array mapped trie (HAMT) representation under the hood, making maps the right default for most key-value data, not just an alternative to keyword lists.

  • Maps store unique keys of any type with %{key => value} or %{key: value} shorthand for atoms.
  • map.key and map[key] read values; Map.fetch/2 returns {:ok, value} or :error.
  • %{map | key: value} updates an existing key efficiently but raises if the key is absent.
  • Map.put/3 adds or overwrites a key; Map.put_new/3 only adds if absent.
  • Partial pattern matching like %{name: name} = user destructures only the keys you need.
  • get_in/2, put_in/3, and update_in/3 simplify working with nested map structures.
  • Structs are fixed-shape maps defined with defstruct, trading flexibility for compile-time safety.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ElixirStudyNotes#MapsInElixir#Maps#Elixir#Creating#Updating#StudyNotes#SkillVeris#ExamPrep