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

Hot Code Reloading

Understand how the BEAM VM loads new module versions into a running system without downtime, and how to do it safely.

Practical ErlangAdvanced10 min readJul 10, 2026
Analogies

What Hot Code Reloading Is

The BEAM VM can hold at most two versions of a compiled module in memory at once: the current version and one old version. When code:load_file/1 (or l/1 in the shell) loads a new version, it doesn't stop any running process — processes already executing inside that module keep running whichever version they were on until they make a call that resolves to the newest code. This is what makes hot code reloading possible: you can push a new module into a live system without restarting the node or dropping any in-flight connections.

🏏

Cricket analogy: The BEAM keeping an 'old' and 'new' version of a module resident is like a franchise keeping both last season's and this season's playing squad on the roster during a transfer window, only retiring the old squad once every match currently in progress has finished.

Local vs Fully Qualified Calls

The mechanism behind this is the distinction between local and fully-qualified calls. A local call like f(X) inside a loop resolves at the version the running process's code was compiled against and keeps executing that version even after a newer one loads. A fully-qualified call like Module:f(X) is resolved fresh on every invocation against whatever version is currently loaded. Long-running server loops are therefore often written to make an explicit fully-qualified 'tail call' back into themselves so they naturally pick up new code on their next iteration.

🏏

Cricket analogy: A local call staying on the old module version is like a bowler sticking to the exact run-up rhythm they started their over with, while a fully-qualified call is like checking the scoreboard between overs and immediately adopting the newly announced field restrictions.

erlang
%% counter.erl  a gen_server that supports hot code upgrade
-module(counter).
-behaviour(gen_server).
-export([start_link/0, bump/0]).
-export([init/1, handle_call/3, handle_cast/2, code_change/3]).

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
bump() -> gen_server:call(?MODULE, bump).

init(N) -> {ok, N}.

handle_call(bump, _From, N) ->
    {reply, N + 1, N + 1}.

handle_cast(_Msg, N) -> {noreply, N}.

%% Invoked automatically during a release upgrade so state
%% from the OLD version can be migrated to the NEW version's shape
code_change(_OldVsn, N, _Extra) when is_integer(N) ->
    {ok, #{count => N, upgraded => true}}.

Using appup and relup for Release Upgrades

For a coordinated production upgrade, each application version pair needs an .appup file describing exactly how to transition — instructions like {update, Module, {advanced, Extra}} for stateful modules or {load_module, Module} for stateless ones. rebar3's relx/relup tooling reads these .appup files and compiles them, together with the old and new releases, into a single .relup file. OTP's release_handler then applies that .relup on the running node, loading new code, migrating process state, and unloading the old version once the transition completes.

🏏

Cricket analogy: An .appup file listing precise upgrade instructions per module is like a team's tour dossier specifying exactly which players get added, dropped, or retained between two specific series, rather than a vague 'refresh the squad.'

The BEAM only ever keeps two versions of a module loaded — current and old. Loading a third version forces an implicit purge of the oldest, which kills any process still running purely on that version. In a hot upgrade, always drive long-lived processes like gen_servers through their code_change/3 callback so they migrate off the old version before the next reload.

Practical Considerations

Hot upgrades work cleanly when a module's exported behaviour and state shape are unchanged or the code_change/3 callback fully accounts for the difference; they get complicated quickly when a gen_server's internal state record changes shape, when ETS table structures change, or when several interdependent applications must upgrade in lockstep. Because of that operational complexity, many teams today prefer rolling deploys behind a load balancer for ordinary services, reserving true hot code upgrades for systems — like telecom switches — where dropping any in-flight process is unacceptable.

🏏

Cricket analogy: Relying on code_change/3 to migrate a gen_server's internal state is like a captain needing to physically hand over the exact match situation — score, overs left, field placements — to an incoming stand-in captain mid-match, not just swap the name on the team sheet.

code:soft_purge/1 removes an old module version only if no process is currently executing it, returning false (and leaving it in place) if a process is still running old code — safer than code:purge/1, which unconditionally kills any process still executing the purged version.

  • The BEAM VM can hold at most two versions of a module in memory simultaneously: the current version and one old version.
  • Local function calls within a running process continue on whichever module version was loaded when the process's code started executing.
  • A fully-qualified call (Module:Function(Args)) always jumps to the newest loaded version of that module.
  • code:load_file/1 (or l/1 in the shell) loads a new module version; code:purge/1 forcibly removes the old version, killing lingering processes.
  • code:soft_purge/1 removes the old version only if no process is still executing it, avoiding an unsafe kill.
  • .appup files describe per-version upgrade/downgrade instructions; rebar3's relup/relx tooling assembles them into a release upgrade package.
  • gen_server implementations should handle code_change/3 to migrate internal state safely across a hot upgrade.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ErlangStudyNotes#HotCodeReloading#Hot#Code#Reloading#Local#StudyNotes#SkillVeris#ExamPrep