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

Functions and Modules

A practical guide to organizing Erlang code into modules and functions, covering declarations, multiple clauses, arity, and exporting a public API.

Core ConceptsBeginner7 min readJul 10, 2026
Analogies

Introduction to Modules and Functions

Erlang code is organized into modules, and every module is simply a text file containing function definitions, compiled independently and identified by its own name. A module groups related functions together the way a toolbox groups related tools, giving you a namespace so that two different modules can each define a function called start/0 without colliding. Functions themselves are the fundamental unit of behavior in Erlang: since Erlang has no loops or mutable variables, virtually all logic is expressed as functions calling other functions, often recursively.

🏏

Cricket analogy: A module is like an IPL franchise: Mumbai Indians and Chennai Super Kings can each have a player called 'Kumar' without confusion, because the franchise name disambiguates them, just as a module name disambiguates same-named functions.

Defining Modules

Every Erlang source file must begin with a -module(Name) attribute, and the file itself must be named Name.erl — the compiler enforces this match, so a module declared -module(shopping_cart) has to live in shopping_cart.erl. Compiling with c(shopping_cart) in the shell, or erlc shopping_cart.erl from the command line, produces a shopping_cart.beam file that the BEAM virtual machine loads and executes; nothing runs until that byte-code file exists.

🏏

Cricket analogy: Just as a player's registered team must match the jersey they wear on the field — you can't play for Mumbai Indians in a Chennai Super Kings jersey — a -module(shopping_cart) declaration must match the file shopping_cart.erl exactly.

erlang
-module(shopping_cart).
-export([new/0, add_item/2, total/1, greet/1, greet/2]).

new() ->
    [].

add_item(Cart, {Name, Price}) ->
    [{Name, Price} | Cart].

total(Cart) ->
    lists:foldl(fun({_Name, Price}, Sum) -> Price + Sum end, 0, Cart).

greet(Name) ->
    io_lib:format("Hello, ~s!", [Name]).

greet(Name, Title) ->
    io_lib:format("Hello, ~s ~s!", [Title, Name]).

Function Clauses and Arity

A single function name can have multiple clauses, each with a different pattern in its head, and Erlang tries each clause in order until one matches the arguments — this is how classify(0) -> zero; classify(N) when N > 0 -> positive; classify(_) -> negative. selects behavior without an if/else chain. Crucially, a function's identity in Erlang is the pair name/arity, so greet/1 and greet/2 are treated as completely unrelated functions even though they share a name, which is why you'll see them referenced as greet/1 in documentation and error messages.

🏏

Cricket analogy: Selecting a bowler for the next over is like Erlang trying function clauses in order: the captain checks conditions — is it a pace-friendly pitch, is a lefty batting — and picks the first bowler whose matching condition fits, just as classify/1 tries each clause top to bottom.

In Erlang documentation and error messages you'll always see functions written as name/arity, such as greet/1 or lists:map/2 — the arity is not optional trivia, it's part of the function's identity, so greet/1 and greet/2 can have completely different bodies and behavior.

Exporting and Calling Functions

The -export([Name/Arity, ...]) attribute lists which functions are visible outside the module; anything left off that list is private and can only be called by other functions inside the same file, giving you a clean public API similar to how a library exposes only certain headers. Calling a function from outside its own module always requires the qualified form Module:Function(Args), such as lists:reverse(L) or shopping_cart:add_item(Cart, Item), whereas calls within the same module can omit the module prefix.

🏏

Cricket analogy: The -export list is like the playing XI announced before a match — only those 11 named players can take the field, while the rest of the squad stays in the pavilion, unreachable to the opposition, just as unexported functions stay unreachable outside the module.

Calling an unexported (private) function from another module, or getting the arity wrong, produces an undef error at runtime rather than a compile-time warning in the shell — always double check your -export list matches exactly what you intend other modules to call.

  • Every .erl file must declare -module(Name) matching its filename, and compiles to a Name.beam file.
  • Functions are identified by name/arity together — foo/1 and foo/2 are unrelated functions.
  • A function can have multiple clauses; Erlang tries them top to bottom and runs the first one whose pattern matches.
  • The -export([Name/Arity, ...]) attribute controls which functions are callable from outside the module.
  • Calls to functions in another module require the qualified form Module:Function(Args).
  • Functions not listed in -export are private and only callable from within the same module.
  • Compiling with c(Module) in the shell or erlc Module.erl produces the loadable .beam file.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ErlangStudyNotes#FunctionsAndModules#Functions#Modules#Defining#Function#StudyNotes#SkillVeris#ExamPrep