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

Records in Erlang

Records give named, compile-time-checked structure to tuples, making Erlang code more readable and less error-prone when working with structured data.

Data & RecordsBeginner8 min readJul 10, 2026
Analogies

Introduction to Records

A record in Erlang is a compile-time construct that attaches field names to the elements of a tuple, so instead of remembering that element 2 of a 4-tuple is an age you write Person#person.age. Records are defined with the -record attribute, for example -record(person, {name, age = 0, email}), and every field can carry a default value that is used whenever a field is omitted when the record is created.

🏏

Cricket analogy: A scorecard entry like {a, 47, 2} tells you little on its own, but naming the fields runs, fours, wickets the way a record names person's name and age turns a raw tuple like Sachin Tendulkar's stats into something a commentator can read directly.

Defining and Creating Records

Creating an instance of a record uses the same #name{} syntax with field values supplied by name rather than position: Ada = #person{name = "Ada Lovelace", age = 36}. Any field left out of the expression, such as email in this example, is automatically filled in with its declared default (or the atom undefined if no default was given), which means adding a new field to a record definition later does not break existing code that constructs values of that record.

🏏

Cricket analogy: Selecting a T20 XI, you specify only the players who differ from the usual lineup, say swapping in Jasprit Bumrah for an injured pacer, and the rest default to last match's XI, the same way #person{age=36} overrides one field while the rest fall back to defaults.

Accessing, Updating, and Pattern-Matching Records

Fields are read with dot notation, Ada#person.age, which the compiler expands at build time into a direct tuple-element lookup, so there is zero runtime overhead compared to hand-written tuple indexing. The far more idiomatic approach, however, is pattern matching directly on the record in a function head or a match expression, such as greet(#person{name = Name}) -> io_lib:format("Hello, ~s", [Name]), which both extracts the field and asserts the value's shape in one step.

🏏

Cricket analogy: A third umpire reviewing a run-out doesn't scan the whole scoreboard; they zoom straight to the stumps-broken timestamp, the same targeted extraction Ada#person.age performs by compiling directly to the age slot instead of scanning the whole tuple.

Because Erlang data is immutable, 'updating' a record actually produces a brand-new record value: Older = Ada#person{age = 37} copies every field from Ada except age, which is set to 37, leaving the original Ada binding untouched. This copy-on-update semantics is identical to how tuples and maps behave in Erlang, and it means you can safely pass Ada around to other processes even after creating Older, since nothing can mutate Ada out from under a concurrent reader.

🏏

Cricket analogy: When a scorer corrects a misrecorded boundary, they don't erase history, they issue an amended scorecard for that over while the original over's record stands, the same copy-on-update behavior Ada#person{age=37} exhibits by producing Older without altering Ada.

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

-record(person, {name, age = 0, email}).

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

greet(#person{name = Name, email = undefined}) ->
    io:format("Hello ~s, no email on file~n", [Name]);
greet(#person{name = Name, email = Email}) ->
    io:format("Hello ~s <~s>~n", [Name, Email]).

Records are purely a compile-time feature, record_info(fields, person) and the #person{} syntax only work because the compiler has seen -record(person, {...}) in the current module or an included header file; there is no way to inspect a record's field names at runtime from arbitrary code.

Records vs Tuples: Trade-offs

Under the hood a record is nothing more than a tagged tuple, #person{name="Ada", age=36} compiles to {person, "Ada", 36}, so record definitions must be visible to the compiler wherever they are used, typically via -include("person.hrl") or by defining -record in the same module, because there is no runtime metadata attached to the value itself. This also means two records with the same shape but different names, or the same name defined differently in two modules, are easy sources of confusing bugs, and it is why library authors often expose accessor functions instead of raw record fields when a record crosses a module boundary.

🏏

Cricket analogy: A team sheet handed to the opposition captain is only useful if both sides agree in advance which jersey number maps to which player; the same shared-definition requirement applies to Erlang records, which need -include("person.hrl") in both modules or the tagged tuple becomes meaningless.

Forgetting to update every -include of a shared .hrl file after adding a field is a classic source of {badrecord, person} runtime errors, always recompile every module that includes a changed record header before deploying.

  • Records are compile-time sugar over tuples: #person{name=X} expands to {person, X, ...}.
  • Define records with -record(name, {field1, field2 = Default}); omitted fields fall back to their declared default or undefined.
  • Field access via Rec#name.field and pattern matching via #name{field = Var} both compile to direct tuple-index operations with no runtime cost.
  • Updating a record with Rec#name{field = NewVal} produces a new immutable value; the original binding is never mutated.
  • Record definitions must be visible at compile time (same module or -include'd .hrl); there is no runtime record metadata.
  • Because records are just tagged tuples, mismatched or stale record definitions across modules cause hard-to-diagnose {badrecord, Name} errors.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ErlangStudyNotes#RecordsInErlang#Records#Erlang#Defining#Creating#StudyNotes#SkillVeris#ExamPrep