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

Maps in Erlang

Maps are Erlang's key-value associative data structure, offering dynamic keys, efficient lookups, and pattern matching without requiring a compile-time schema like records do.

Data & RecordsBeginner9 min readJul 10, 2026
Analogies

What Is a Map?

A map in Erlang is a native associative collection written #{Key1 => Value1, Key2 => Value2}, introduced in Erlang/OTP 17 as a more flexible alternative to records and proplists. Unlike a record, a map carries its keys as part of the runtime value itself, so you can add, remove, or iterate over keys dynamically without ever declaring a schema, and keys can be any term, atoms, strings, tuples, or even other maps, not just compile-time-known atoms.

🏏

Cricket analogy: A live match app's stats panel can add a new field like 'DRS reviews remaining' mid-innings without redeploying the app, the same runtime flexibility a map offers by letting you add #{reviews => 2} to a map without any prior schema declaration.

Creating, Updating, and Removing Entries

Adding a fresh key or overwriting an existing one both use the => operator inside an update expression, Map2 = Map#{age => 37}, which will insert age if it wasn't present or overwrite it if it was. The stricter := operator, by contrast, only overwrites, Map#{age := 37} raises a {badkey, age} error if age is not already a key in Map, which is useful when you want to assert that you are updating, not accidentally inserting, a field.

🏏

Cricket analogy: A scorer using => to log a new bowler's figures either creates a fresh line for a debutant or updates an existing bowler's over count, while := is like insisting a substitution can only replace a player already named on the team sheet, erroring if they weren't listed.

erlang
-module(map_demo).
-export([demo/0]).

demo() ->
    Ada = #{name => "Ada Lovelace", age => 36},
    Older = Ada#{age => 37},
    io:format("Older: ~p~n", [Older]),

    case Ada of
        #{name := Name, age := Age} ->
            io:format("~s is ~p~n", [Name, Age])
    end,

    Email = maps:get(email, Ada, "no-email-on-file"),
    io:format("Email: ~s~n", [Email]),

    Ada2 = maps:merge(Ada, #{email => "ada@example.com", title => "Countess"}),
    io:format("Keys: ~p~n", [maps:keys(Ada2)]).

Pattern Matching on Maps

Maps support pattern matching directly in function heads and match expressions using the := operator: #{name := Name, age := Age} = Person extracts both fields and simultaneously asserts they are present, while a key that might be missing is safer to read with maps:get(email, Person, none) or maps:find(email, Person), which returns {ok, Value} or the atom error instead of crashing.

🏏

Cricket analogy: Selecting an XI by pattern-matching #{captain := C, wicketkeeper := WK} = Squad both extracts the named roles and asserts the squad actually has a designated captain and keeper, the same dual extract-and-assert a strict map pattern performs, while checking for an all-rounder more safely uses maps:find, which won't crash if the role is unfilled.

Map#{key := Value} raises {badkey, key} if key is not already present, reach for => when you're not sure the key exists yet, and reserve := for updates where you want a crash to signal a genuine bug rather than silently inserting a typo'd key.

Maps vs Records vs Proplists

Compared to a proplist ([{name, "Ada"}, {age, 36}]), a map offers O(1) average lookup instead of a linear scan through the list, native equality and ordering (two maps with the same key-value pairs are always ==, regardless of insertion order), and a rich standard library in the maps module covering fold, filter, merge, and iteration. Compared to a record, a map trades away compile-time field-name checking for runtime flexibility, which makes maps the better default for data with a dynamic or evolving shape, such as JSON payloads decoded from an HTTP API, while records remain preferable when you want the compiler to catch a typo'd field name at build time.

🏏

Cricket analogy: Looking up a player's strike rate in a well-indexed stats database is instant, unlike flipping through a printed scorebook page by page, the same O(1) map lookup versus proplist's linear scan; but a rigid official scorecard template still catches a mis-entered category the way a record catches a typo'd field.

Since OTP 21, maps preserve insertion order for small maps internally but you should never rely on iteration order, use maps:fold/3, maps:map/2, or maps:filter/2, which make no ordering guarantees, and if order genuinely matters, sort the result of maps:to_list/1 explicitly.

  • A map is written #{Key => Value, ...} and, unlike a record, carries its keys as part of the runtime value with no compile-time schema.
  • Use => to insert-or-update a key; use := to update-only, which raises {badkey, Key} if the key isn't already present.
  • Pattern match with #{key := Var} = Map to extract and assert presence in one step; use maps:get/3 or maps:find/2 for safe, non-crashing optional lookups.
  • Maps give O(1) average-case lookup and are always structurally comparable, unlike order-sensitive proplists.
  • The maps module provides fold/3, map/2, filter/2, merge/2, keys/1, values/1, and to_list/1 for working with map data.
  • Maps are the better default for dynamic or evolving data like decoded JSON; records are better when you want compile-time field-name checking.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ErlangStudyNotes#MapsInErlang#Maps#Erlang#Map#Creating#StudyNotes#SkillVeris#ExamPrep