The call stack helps JavaScript keep track of “where it is” in the code — which function is running now and which one should run next.
🧠 What the Call Stack Does
It starts with a global execution context (your main script).
Every time a function is called, a new function execution context is added.
It works like a stack of plates → Last In, First Out (LIFO).
⚙️ How It Works (Easy Steps)
Start script → JavaScript creates the global context and puts it on the stack.
Function call → A new context is created and pushed on top.
Function calls another → Another context is added on top.
Function finishes → Its context is removed (popped off).
When stack is empty → Program is done.
✴️ JavaScript call stack example
Let’s start with the following example:
function add(a, b) {
return a + b;
}
function average(a, b) {
return add(a, b) / 2;
}
let x = average(10, 20);1️⃣ Start of Program
main()(global execution context) is created.It is pushed onto the call stack.
2️⃣ main() Calls average()
A new execution context for
average()is created.It is pushed on top of
main().
3️⃣ Inside average(), it Calls add()
add()function is invoked.JavaScript pushes
add()onto the stack (last-in).
4️⃣ add() Finishes Execution
add()returns a value.Its execution context is removed (popped).
5️⃣ average() Finishes Execution
After receiving result from
add(), it completes.JavaScript pops
average()off the stack.
6️⃣ main() Completes
main()finishes.Global execution context is removed
➡️ The following picture illustrates the overall status of the Call Stack in all steps:
<div class="infographic-callstack">
<style>
.infographic-callstack {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-callstack[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
}
.infographic-callstack * { box-sizing: border-box; }
.infographic-callstack h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-callstack p { margin: 0; }
.infographic-callstack button { font-family: inherit; }
.cst-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.cst-hero { text-align: center; margin-bottom: 1.3rem; }
.cst-hero h1 { font-size: clamp(1.35rem, 4.5vw, 1.8rem); }
.cst-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.85rem; }
.cst-code { border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.65rem 0.85rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; line-height: 1.8; margin-bottom: 1.4rem; }
.cst-code .ln { display: flex; gap: 0.6rem; padding: 0.05rem 0.3rem; border-radius: 3px; transition: background 0.3s ease, color 0.3s ease; color: var(--ink-soft); }
.cst-code .ln .num { opacity: 0.5; width: 1.1em; flex-shrink: 0; }
.cst-code .ln.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
/* stack visual */
.cst-stagewrap { display: flex; justify-content: center; }
.cst-stage { width: 190px; text-align: center; }
.cst-stage-label { font-family: 'Fraunces', Georgia, serif; font-size: 0.95rem; margin-bottom: 0.5rem; }
.cst-box { position: relative; height: 210px; border: 1.5px solid var(--line); background: var(--paper-alt); }
.cst-frame {
position: absolute; left: 8px; right: 8px; height: 44px;
border: 1.5px solid var(--accent); background: var(--accent-soft); color: var(--accent);
display: flex; align-items: center; justify-content: center;
font-family: 'IBM Plex Mono', monospace; font-weight: 700; font-size: 0.78rem;
opacity: 0; transform: translateY(14px) scale(0.94);
transition: opacity 0.4s ease, transform 0.4s ease, border-color 0.4s ease, background 0.4s ease;
}
.cst-frame.is-active { opacity: 1; transform: translateY(0) scale(1); }
.cst-frame[data-frame="main"] { bottom: 8px; border-color: var(--ink-soft); background: var(--paper); color: var(--ink); }
.cst-frame[data-frame="average"] { bottom: 60px; }
.cst-frame[data-frame="add"] { bottom: 112px; }
.cst-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; color: var(--ink-soft); opacity: 0; transition: opacity 0.4s ease; }
.cst-empty.is-shown { opacity: 1; }
.cst-controls { display: flex; align-items: center; justify-content: center; gap: 0.8rem; margin-top: 1.3rem; }
.cst-nav { border: 1px solid var(--line); background: var(--paper-alt); color: var(--ink); padding: 0.4rem 0.9rem; font-size: 0.78rem; font-weight: 600; cursor: pointer; }
.cst-nav:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.cst-nav:disabled { opacity: 0.35; cursor: default; }
.cst-step-count { font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; color: var(--ink-soft); min-width: 64px; text-align: center; }
.cst-caption { text-align: center; font-size: 0.84rem; color: var(--ink-soft); padding-top: 1.2rem; min-height: 2.6em; width: 100%; }
.cst-caption strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-callstack *, .infographic-callstack *::before, .infographic-callstack *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="cst-wrap">
<div class="cst-hero">
<h1>The call stack, one push and pop at a time</h1>
<p>Step through the same example line by line.</p>
</div>
<div class="cst-code" id="cstCode">
<div class="ln" data-line="1"><span class="num">1</span><span>function add(a, b) { return a + b; }</span></div>
<div class="ln" data-line="2"><span class="num">2</span><span>function average(a, b) { return add(a, b) / 2; }</span></div>
<div class="ln" data-line="3"><span class="num">3</span><span>let x = average(10, 20);</span></div>
</div>
<div class="cst-stagewrap">
<div class="cst-stage">
<div class="cst-stage-label">Call Stack</div>
<div class="cst-box">
<div class="cst-frame" data-frame="add">add()</div>
<div class="cst-frame" data-frame="average">average()</div>
<div class="cst-frame" data-frame="main">main()</div>
<div class="cst-empty" id="cstEmpty">stack empty</div>
</div>
</div>
</div>
<div class="cst-controls">
<button class="cst-nav" id="cstPrev" type="button">◂ Prev</button>
<span class="cst-step-count" id="cstCount">Step 1 of 6</span>
<button class="cst-nav" id="cstNext" type="button">Next ▸</button>
</div>
<p class="cst-caption" id="cstCaption"></p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-callstack')
|| document.querySelector('.infographic-callstack');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const steps = [
{ stack: ['main'], line: 3, caption: '<strong>main()</strong> — the global execution context — is created and pushed onto the stack.' },
{ stack: ['main', 'average'], line: 3, caption: '<strong>main()</strong> calls <strong>average()</strong>. A new execution context is pushed on top.' },
{ stack: ['main', 'average', 'add'], line: 2, caption: '<strong>average()</strong> calls <strong>add()</strong>. Last in, so it goes on top of the stack.' },
{ stack: ['main', 'average'], line: 1, caption: '<strong>add()</strong> returns its value — its execution context is popped off.' },
{ stack: ['main'], line: 2, caption: '<strong>average()</strong> finishes with the result and is popped off.' },
{ stack: [], line: null, caption: '<strong>main()</strong> completes. The global execution context is removed — <strong>x = 15</strong>.' },
];
let index = 0;
const frames = wrapper.querySelectorAll('.cst-frame');
const lines = wrapper.querySelectorAll('.cst-code .ln');
const empty = wrapper.querySelector('#cstEmpty');
const caption = wrapper.querySelector('#cstCaption');
const count = wrapper.querySelector('#cstCount');
const prevBtn = wrapper.querySelector('#cstPrev');
const nextBtn = wrapper.querySelector('#cstNext');
function render() {
const step = steps[index];
frames.forEach(f => f.classList.toggle('is-active', step.stack.includes(f.dataset.frame)));
lines.forEach(l => l.classList.toggle('is-active', Number(l.dataset.line) === step.line));
empty.classList.toggle('is-shown', step.stack.length === 0);
caption.innerHTML = step.caption;
count.textContent = 'Step ' + (index + 1) + ' of ' + steps.length;
prevBtn.disabled = index === 0;
nextBtn.disabled = index === steps.length - 1;
}
prevBtn.addEventListener('click', () => { if (index > 0) { index -= 1; render(); } });
nextBtn.addEventListener('click', () => { if (index < steps.length - 1) { index += 1; render(); } });
render();
})();
</script>Stack Overflow
The call stack is a structure used by the JavaScript engine to keep track of function calls.
If functions keep calling themselves without ever stopping, the stack fills up.
Once it exceeds the maximum stack size, the engine throws a
RangeError.
For example, when you execute a recursive function that has no exit condition, the JavaScript engine will issue a stack overflow error:
function fn() {
fn();
}
fn();// stack overflow🔍 Explanation:
The function
fn()calls itself recursively.There's no termination condition (base case), so it keeps calling itself infinitely.
Each function call is added to the call stack, a structure that keeps track of function calls.
Eventually, the call stack becomes too large, exceeding its size limit.
Uncaught RangeError: Maximum call stack size exceeded