Two Core Data Structures
Lists and tuples are the two workhorse compound data types in Erlang, and choosing between them is one of the first design decisions you make when modeling data. A list, written [1, 2, 3], is a variable-length, homogeneous-in-spirit sequence built for holding an unknown or changing number of items and processing them one at a time; a tuple, written {ok, 42}, is a fixed-size, heterogeneous container built for grouping a known, small number of related values together, like a lightweight record.
Cricket analogy: A list is like a bowling figures scorecard that grows ball by ball through an entire innings — you don't know the final length until the innings ends — while a tuple is like a fixed player profile {Name, JerseyNumber, Role} with exactly three fixed fields every time.
Working with Lists
Internally a list is a chain of cons cells, so [1, 2, 3] is really 1 followed by the list [2, 3], and pattern matching it as [H|T] gives you the head 1 and tail [2, 3] — this is the mechanism every list-processing recursive function relies on. The ++ operator concatenates two lists but has to walk and copy the entire left-hand list, so repeatedly appending in a loop is O(N^2) overall; the standard library's lists module (lists:reverse/1, lists:map/2, lists:sort/1, lists:member/2) provides efficient, well-tested operations that you should prefer over hand-rolled equivalents.
Cricket analogy: Walking [H|T] is like a scorer reading the over ball by ball — first ball (head), then the rest of the over (tail) — and stitching two overs together with ++ means re-reading every ball of the first over each time, which is why experienced scorers use the standard scorebook format (the lists module) instead of manually re-tallying.
-module(data_shapes).
-export([describe/1, read_config/1, merge_names/2]).
describe(List) when is_list(List) ->
{list, length(List)};
describe(Tuple) when is_tuple(Tuple) ->
{tuple, tuple_size(Tuple)}.
read_config(Path) ->
case file:read_file(Path) of
{ok, Data} -> {ok, Data};
{error, Reason} -> {error, Reason}
end.
merge_names([H | T], Acc) -> merge_names(T, [H | Acc]);
merge_names([], Acc) -> lists:reverse(Acc).tuple_size/1 and length/1 both run in different time complexities worth remembering: tuple_size/1 is O(1) because a tuple stores its arity directly in its header, while length/1 is O(N) because a list has to be walked cell by cell to count its elements.
Working with Tuples
Tuples are accessed by position rather than by walking, so element(1, {ok, 42}) or pattern matching {Status, Value} = {ok, 42} both run in constant time regardless of the tuple's size, unlike indexing into a list. The single most common tuple idiom in Erlang is the tagged tuple used for return values — {ok, Result} on success and {error, Reason} on failure — which lets calling code pattern-match directly on the outcome, as in case file:read_file(Path) of {ok, Data} -> ...; {error, Reason} -> ... end.
Cricket analogy: Reading a fixed player-profile tuple {Name, Jersey, Role} is like glancing at position 2 on a scoreboard card and instantly knowing it's the jersey number, no scanning needed; and the {ok, Runs} versus {out, Reason} pattern is exactly how a commentary feed tags each ball's outcome for instant recognition.
Choosing Between Lists and Tuples
Use a list when the number of elements is variable or unknown ahead of time, or when you need to walk every element — processing a stream of log lines, a queue of jobs, a variable-length string. Use a tuple when you have a fixed, known number of fields that belong together conceptually, especially when you want to tag the data's meaning, such as {point, X, Y} versus {circle, X, Y, Radius}; reaching for the wrong one, like using a tuple for a growing collection, forces awkward code because tuples have no efficient way to add or remove elements.
Cricket analogy: A growing partnership's ball-by-ball log belongs in a list since its length is unknown until a wicket falls, but a single delivery's outcome, {wide, 1} or {four, 4}, belongs in a tuple — trying to force the whole innings log into one giant fixed tuple would be as awkward as printing a scorecard with no room to add overs.
Tuples have no efficient 'add one element' operation — erlang:append_element/2 exists but copies the entire tuple to build a new one, so if your data grows or shrinks dynamically, reach for a list (or a map) instead of trying to force a tuple to act like a resizable collection.
- Lists are variable-length sequences built from cons cells; tuples are fixed-size, positionally-addressed containers.
- Pattern matching [H|T] splits a list into its head and tail, the basis of list recursion.
- The ++ operator copies its left-hand list, making repeated concatenation an O(N^2) pattern to avoid.
- The lists module (map/2, filter/2, foldl/3, reverse/1, sort/1, member/2) provides efficient, tested list operations.
- Tuple element access via element/2 or pattern matching is O(1) regardless of tuple size.
- The tagged-tuple idiom, {ok, Result} / {error, Reason}, is the standard way Erlang functions signal success or failure.
- Choose lists for variable-length or walked data, and tuples for a small, fixed number of related fields.
Practice what you learned
1. How is the list [1,2,3] structured internally?
2. Why is repeated use of ++ inside a loop a performance concern?
3. What does the tagged tuple {error, Reason} idiomatically represent?
4. What is the time complexity of tuple_size/1 compared to length/1 on a list?
5. When should you prefer a tuple over a list for modeling data?
Was this page helpful?
You May Also Like
Functions and Modules
A practical guide to organizing Erlang code into modules and functions, covering declarations, multiple clauses, arity, and exporting a public API.
Recursion in Erlang
Why recursion replaces loops in Erlang, and how to write correct, tail-recursive functions that run in constant stack space.
Higher-Order Functions in Erlang
How Erlang treats functions as first-class values, enabling anonymous funs, closures, and the standard library's map/filter/foldl.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics