🔄 Recursion in Action
A recursive function is a function that calls itself repeatedly to solve a problem by breaking it into smaller parts. This process, known as recursion, continues until a specific stopping condition (called the base case) is met, which prevents the function from running forever.
🧠 Why Use Recursion?
Recursive functions are useful for:
Breaking down complex problems into simpler subproblems.
Solving problems involving:
Mathematical calculations (factorials, sum, Fibonacci).
Data structures like trees, graphs.
Algorithms such as quicksort, binary search, backtracking.
Example :
Suppose that you have a function called recurse(). The recurse() is a recursive function if it calls itself inside its body, like this:
function recurse() {
// ...
recurse();
// ...
}A recursive function always has a condition to stop calling itself. Otherwise, it will call itself indefinitely. So a recursive function typically looks like the following:
function recurse() {
if (condition) {
// Base case: stop here
} else {
recurse(); // Recursive call
}
}Base Case: The condition that stops the recursion.
Recursive Case: The function calls itself to solve a smaller problem.
Without a base case, the function calls itself forever, causing a RangeError.
<div class="infographic-recursion">
<style>
.infographic-recursion {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--line: #ddd6c9;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--ok: #1a7f4e;
--ok-soft: #e3f5ec;
--warn: #b45309;
--warn-soft: #fdf0dc;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', -apple-system, sans-serif;
--mono-font: 'IBM Plex Mono', 'Courier New', monospace;
margin: 0 auto;
box-sizing: border-box;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.infographic-recursion[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--line: #2c313d;
--accent: #5b8def;
--accent-soft:#202942;
--ok: #5cd6a0;
--ok-soft: #16342a;
--warn: #e0a458;
--warn-soft: #3a2c14;
}
.infographic-recursion * { box-sizing: border-box; }
.infographic-recursion .ig-inner { padding: 1.75rem clamp(1.25rem, 5vw, 3rem) 2rem; }
.infographic-recursion .ig-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 4.5vw, 1.9rem);
font-weight: 600; text-align: center; margin: 0 0 1.25rem;
}
.infographic-recursion .code-block {
font-family: var(--mono-font); font-size: 0.82rem; line-height: 1.7;
background: var(--paper-alt); border: 1px solid var(--line); border-radius: 6px;
padding: 0.9rem 1rem; white-space: pre; margin-bottom: 1.5rem;
}
.infographic-recursion .code-block .kw { color: var(--accent); }
.infographic-recursion .code-block .base { color: var(--ok); font-weight: 700; }
.infographic-recursion .code-block .rec { color: var(--warn); font-weight: 700; }
.infographic-recursion .code-block .comment { color: var(--ink-soft); }
.infographic-recursion .controls { display: flex; align-items: center; justify-content: center; gap: 0.6rem; flex-wrap: wrap; padding-top: 1.4rem; }
.infographic-recursion .n-wrap { position: relative; display: flex; align-items: center; height: 2.4rem; }
.infographic-recursion .n-tag {
position: absolute; top: -1.3rem; left: 50%; transform: translateX(-50%);
font-size: 0.65rem; font-family: var(--mono-font); color: var(--accent);
background: var(--accent-soft); padding: 0.1rem 0.4rem; border-radius: 4px; white-space: nowrap;
}
.infographic-recursion .n-input {
width: 4rem; height: 2.4rem; font-family: var(--mono-font); font-size: 1rem; font-weight: 700;
text-align: center; border: 2px solid var(--accent); background: var(--accent-soft); color: var(--accent);
border-radius: 6px; outline: none;
}
.infographic-recursion .ctrl-btn {
font: inherit; font-size: 0.82rem; font-weight: 600; border: 1px solid var(--line);
background: var(--paper-alt); color: var(--ink); padding: 0.45rem 1rem;
border-radius: 999px; cursor: pointer;
}
.infographic-recursion .ctrl-btn:hover { border-color: var(--accent); color: var(--accent); }
.infographic-recursion .ctrl-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.infographic-recursion .stack-wrap { margin-top: 1.75rem; min-height: 15rem; }
.infographic-recursion .frame {
display: flex; align-items: center; gap: 0.6rem;
font-family: var(--mono-font); font-size: 0.8rem;
border: 1px solid var(--line); background: var(--paper-alt);
padding: 0.5rem 0.7rem; border-radius: 6px; margin-bottom: 0.4rem;
opacity: 0; transform: translateY(-8px) scale(0.95);
transition: opacity 0.3s ease, transform 0.3s ease, background 0.3s ease, border-color 0.3s ease;
}
.infographic-recursion .frame.is-in { opacity: 1; transform: translateY(0) scale(1); }
.infographic-recursion .frame.is-active { border-color: var(--accent); box-shadow: 0 3px 8px rgba(0,0,0,0.1); }
.infographic-recursion .frame.is-base { border-color: var(--ok); background: var(--ok-soft); }
.infographic-recursion .frame.is-returned { border-color: var(--ok); background: var(--ok-soft); opacity: 0.55; }
.infographic-recursion .frame-call { font-weight: 700; color: var(--ink); white-space: nowrap; }
.infographic-recursion .frame-status { color: var(--ink-soft); font-size: 0.75rem; }
.infographic-recursion .frame-status strong { color: var(--ok); }
.infographic-recursion .result-line {
text-align: center; font-family: var(--mono-font); font-size: 0.95rem; font-weight: 700;
margin-top: 1rem; padding: 0.6rem; border-radius: 6px;
background: var(--ok-soft); color: var(--ok); border: 1px solid var(--ok);
opacity: 0; transform: scale(0.9); transition: opacity 0.3s ease, transform 0.3s ease;
}
.infographic-recursion .result-line.is-visible { opacity: 1; transform: scale(1); }
.infographic-recursion .caption {
text-align: center; font-size: 0.85rem; color: var(--ink-soft); margin-top: 1rem; min-height: 1.2em;
}
.infographic-recursion .caption strong { color: var(--ink); font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-recursion *, .infographic-recursion *::before, .infographic-recursion *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="ig-inner">
<h2 class="ig-title">Recursion in Action</h2>
<div class="code-block"><span class="kw">function</span> factorial(n) {
<span class="kw">if</span> (n <= 1) <span class="kw">return</span> <span class="base">1</span>; <span class="comment">// base case</span>
<span class="kw">return</span> <span class="rec">n * factorial(n - 1)</span>; <span class="comment">// recursive case</span>
}</div>
<div class="controls">
<div class="n-wrap">
<span class="n-tag">n</span>
<input type="number" class="n-input" id="nInput" value="4" min="1" max="6">
</div>
<button class="ctrl-btn primary" id="playBtn">▶ Play</button>
<button class="ctrl-btn" id="restartBtn">⟲ Restart</button>
</div>
<div class="stack-wrap" id="stackWrap"></div>
<div class="result-line" id="resultLine"></div>
<p class="caption" id="caption">Set n, then press Play to watch the call stack grow and unwind.</p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-recursion')
|| document.querySelector('.infographic-recursion');
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 nInput = wrapper.querySelector('#nInput');
const playBtn = wrapper.querySelector('#playBtn');
const restartBtn = wrapper.querySelector('#restartBtn');
const stackWrap = wrapper.querySelector('#stackWrap');
const resultLine = wrapper.querySelector('#resultLine');
const caption = wrapper.querySelector('#caption');
let frameEls = [];
let timer = null;
let steps = [];
let idx = -1;
function buildSteps(n) {
steps = [];
// Push phase
for (let i = n; i >= 1; i--) {
const isBase = i === 1;
steps.push({
type: 'push', depth: n - i, n: i, isBase,
status: isBase ? 'n = 1 → base case, return 1' : `n = ${i}, calls factorial(${i - 1})`
});
}
// Unwind phase
let value = 1;
for (let i = 1; i <= n; i++) {
const depth = n - i;
if (i === 1) {
steps.push({ type: 'unwind', depth, n: i, text: 'returns 1', value: 1 });
} else {
const newValue = i * value;
steps.push({ type: 'unwind', depth, n: i, text: `${i} * ${value} = ${newValue}`, value: newValue });
value = newValue;
}
}
steps.push({ type: 'done', n, value });
}
function reset() {
stackWrap.innerHTML = '';
frameEls = [];
resultLine.classList.remove('is-visible');
resultLine.textContent = '';
}
function applyStep(step) {
if (step.type === 'push') {
const frame = document.createElement('div');
frame.className = 'frame is-active' + (step.isBase ? ' is-base' : '');
frame.style.marginLeft = `${step.depth * 1.1}rem`;
frame.innerHTML = `<span class="frame-call">factorial(${step.n})</span><span class="frame-status">${step.status}</span>`;
stackWrap.appendChild(frame);
frameEls[step.depth] = frame;
requestAnimationFrame(() => frame.classList.add('is-in'));
frameEls.forEach(f => f && f.classList.remove('is-active'));
frame.classList.add('is-active');
caption.innerHTML = step.isBase
? `<strong>n = 1</strong> hits the base case — time to unwind`
: `<strong>n = ${step.n}</strong> calls <strong>factorial(${step.n - 1})</strong>`;
}
if (step.type === 'unwind') {
const frame = frameEls[step.depth];
frame.classList.remove('is-active');
frame.classList.add('is-returned');
frame.querySelector('.frame-status').innerHTML = `<strong>${step.text}</strong>`;
caption.innerHTML = `<strong>factorial(${step.n})</strong> returns <strong>${step.value}</strong>`;
}
if (step.type === 'done') {
resultLine.textContent = `factorial(${step.n}) = ${step.value}`;
resultLine.classList.add('is-visible');
caption.innerHTML = `Done — unwound all the way back to <strong>factorial(${step.n})</strong>`;
}
}
function stop() {
clearInterval(timer);
timer = null;
playBtn.textContent = '▶ Play';
}
function advance() {
idx++;
if (idx >= steps.length) { stop(); return; }
applyStep(steps[idx]);
if (idx >= steps.length - 1) stop();
}
function play() {
if (timer) { stop(); return; }
if (idx >= steps.length - 1 || idx === -1) {
restart(false);
}
playBtn.textContent = '⏸ Pause';
timer = setInterval(advance, 700);
advance();
}
function restart(clearCaption = true) {
stop();
idx = -1;
reset();
const n = Math.max(1, Math.min(6, Number(nInput.value) || 4));
nInput.value = n;
buildSteps(n);
if (clearCaption) caption.textContent = 'Set n, then press Play to watch the call stack grow and unwind.';
}
playBtn.addEventListener('click', play);
restartBtn.addEventListener('click', () => restart(true));
nInput.addEventListener('change', () => restart(true));
restart(true);
})();
</script>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});✅ Example 1: Count Down Using Recursion
Without Base Case
Here ,
To count down from the number 3 to 1, you can:
show the number 3.
and call the
countDown(2)that shows the number 2.and call the
countDown(1)that shows the number 1.
function countDown(fromNumber) {
console.log(fromNumber);
countDown(fromNumber - 1); // No base case!
}
countDown(3);This countDown(3) will run until the call stack size is exceeded, like this and give output :
This goes into infinite recursion because there’s no condition to stop it.
3
2
1
0
-1
-2
...
Uncaught RangeError: Maximum call stack size exceededThe countdown will stop when the next number is zero.
With Base Case
So, we need to add Condition as follows:
function countDown(fromNumber) {
console.log(fromNumber);
let nextNumber = fromNumber - 1;
if (nextNumber > 0) {
countDown(nextNumber);
}
}
countDown(3);3
2
1Explanation:
countDown(3)→ prints3, callscountDown(2)countDown(2)→ prints2, callscountDown(1)countDown(1)→ prints1, doesn't call again (nextNumber is 0)Recursion stops!
<div class="infographic-recursionbase">
<style>
.infographic-recursionbase {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--line: #ddd6c9;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--ok: #1a7f4e;
--ok-soft: #e3f5ec;
--bad: #b42318;
--bad-soft: #fbe9e7;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', -apple-system, sans-serif;
--mono-font: 'IBM Plex Mono', 'Courier New', monospace;
margin: 0 auto;
box-sizing: border-box;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.infographic-recursionbase[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--line: #2c313d;
--accent: #5b8def;
--accent-soft:#202942;
--ok: #5cd6a0;
--ok-soft: #16342a;
--bad: #e57373;
--bad-soft: #3a2222;
}
.infographic-recursionbase * { box-sizing: border-box; }
.infographic-recursionbase .ig-inner { padding: 1.75rem clamp(1.25rem, 5vw, 3rem) 2rem; }
.infographic-recursionbase .ig-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 4.5vw, 1.9rem);
font-weight: 600; text-align: center; margin: 0 0 1.25rem;
}
/* Toggle switch */
.infographic-recursionbase .toggle-row {
display: flex; align-items: center; justify-content: center; gap: 0.75rem; margin-bottom: 1.25rem;
}
.infographic-recursionbase .toggle-label {
font-family: var(--mono-font); font-size: 0.8rem; font-weight: 700; color: var(--ink-soft); transition: color 0.2s; cursor: pointer;
}
.infographic-recursionbase .toggle-label.active.without { color: var(--bad); }
.infographic-recursionbase .toggle-label.active.with { color: var(--ok); }
.infographic-recursionbase .switch {
position: relative; width: 3.4rem; height: 1.8rem; border-radius: 999px; cursor: pointer;
background: var(--bad); transition: background 0.3s;
}
.infographic-recursionbase .switch.is-on { background: var(--ok); }
.infographic-recursionbase .switch .knob {
position: absolute; top: 0.18rem; left: 0.18rem; width: 1.44rem; height: 1.44rem; border-radius: 50%;
background: #fff; transition: transform 0.3s cubic-bezier(.4,1.4,.6,1); box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.infographic-recursionbase .switch.is-on .knob { transform: translateX(1.6rem); }
.infographic-recursionbase .code-block {
font-family: var(--mono-font); font-size: 0.8rem; line-height: 1.7;
background: var(--paper-alt); border: 1px solid var(--line); border-radius: 6px;
padding: 0.9rem 1rem; white-space: pre; margin-bottom: 1.25rem;
}
.infographic-recursionbase .code-block .kw { color: var(--accent); }
.infographic-recursionbase .code-block .bad-tok { color: var(--bad); font-weight: 700; }
.infographic-recursionbase .code-block .ok-tok { color: var(--ok); font-weight: 700; }
.infographic-recursionbase .code-block .comment { color: var(--ink-soft); }
.infographic-recursionbase .controls { display: flex; justify-content: center; gap: 0.6rem; margin-bottom: 1.25rem; }
.infographic-recursionbase .ctrl-btn {
font: inherit; font-size: 0.82rem; font-weight: 600; border: 1px solid var(--line);
background: var(--paper-alt); color: var(--ink); padding: 0.45rem 1rem;
border-radius: 999px; cursor: pointer;
}
.infographic-recursionbase .ctrl-btn:hover { border-color: var(--accent); color: var(--accent); }
.infographic-recursionbase .ctrl-btn.primary.without-tone { background: var(--bad); color: #fff; border-color: var(--bad); }
.infographic-recursionbase .ctrl-btn.primary.with-tone { background: var(--ok); color: #fff; border-color: var(--ok); }
.infographic-recursionbase .stack-wrap { min-height: 12rem; }
.infographic-recursionbase .frame {
display: flex; align-items: center; gap: 0.6rem;
font-family: var(--mono-font); font-size: 0.8rem;
border: 1px solid var(--line); background: var(--paper-alt);
padding: 0.45rem 0.65rem; border-radius: 6px; margin-bottom: 0.35rem;
opacity: 0; transform: translateY(-8px) scale(0.95);
transition: opacity 0.25s ease, transform 0.25s ease, border-color 0.25s ease, background 0.25s ease;
}
.infographic-recursionbase .frame.is-in { opacity: 1; transform: translateY(0) scale(1); }
.infographic-recursionbase .frame.without-tone { border-color: var(--bad); }
.infographic-recursionbase .frame.with-tone { border-color: var(--ok); }
.infographic-recursionbase .frame-call { font-weight: 700; color: var(--ink); white-space: nowrap; }
.infographic-recursionbase .frame-print { color: var(--ink-soft); font-size: 0.75rem; }
.infographic-recursionbase .frame-print strong.without-tone { color: var(--bad); }
.infographic-recursionbase .frame-print strong.with-tone { color: var(--ok); }
.infographic-recursionbase .crash-box {
margin-top: 0.5rem; text-align: center; font-family: var(--mono-font); font-size: 0.82rem; font-weight: 700;
padding: 0.7rem; border-radius: 6px; background: var(--bad-soft); color: var(--bad); border: 1px solid var(--bad);
opacity: 0; transform: scale(0.9); transition: opacity 0.3s ease, transform 0.3s ease;
}
.infographic-recursionbase .crash-box.is-visible { opacity: 1; transform: scale(1); }
.infographic-recursionbase .crash-box.shake { animation: rb-shake 0.4s ease; }
@keyframes rb-shake { 0%,100% { transform: translateX(0) scale(1); } 25% { transform: translateX(-5px) scale(1); } 75% { transform: translateX(5px) scale(1); } }
.infographic-recursionbase .stop-box {
margin-top: 0.5rem; text-align: center; font-family: var(--mono-font); font-size: 0.82rem; font-weight: 700;
padding: 0.7rem; border-radius: 6px; background: var(--ok-soft); color: var(--ok); border: 1px solid var(--ok);
opacity: 0; transform: scale(0.9); transition: opacity 0.3s ease, transform 0.3s ease;
}
.infographic-recursionbase .stop-box.is-visible { opacity: 1; transform: scale(1); }
.infographic-recursionbase .caption {
text-align: center; font-size: 0.85rem; color: var(--ink-soft); margin-top: 1rem; min-height: 1.2em;
}
.infographic-recursionbase .caption strong { color: var(--ink); font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-recursionbase *, .infographic-recursionbase *::before, .infographic-recursionbase *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="ig-inner">
<h2 class="ig-title">Recursion Needs a Base Case</h2>
<div class="toggle-row">
<span class="toggle-label without active" id="labelWithout">no base case</span>
<div class="switch" id="switch"><div class="knob"></div></div>
<span class="toggle-label with" id="labelWith">with base case</span>
</div>
<div class="code-block" id="codeBlock"></div>
<div class="controls">
<button class="ctrl-btn primary without-tone" id="playBtn">▶ Run countDown(3)</button>
<button class="ctrl-btn" id="restartBtn">⟲ Restart</button>
</div>
<div class="stack-wrap" id="stackWrap"></div>
<div class="crash-box" id="crashBox">Uncaught RangeError: Maximum call stack size exceeded</div>
<div class="stop-box" id="stopBox">✓ nextNumber is 0 — recursion stops cleanly</div>
<p class="caption" id="caption">Press Run to see what happens without a stopping condition.</p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-recursionbase')
|| document.querySelector('.infographic-recursionbase');
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 switchEl = wrapper.querySelector('#switch');
const labelWithout = wrapper.querySelector('#labelWithout');
const labelWith = wrapper.querySelector('#labelWith');
const codeBlock = wrapper.querySelector('#codeBlock');
const playBtn = wrapper.querySelector('#playBtn');
const restartBtn = wrapper.querySelector('#restartBtn');
const stackWrap = wrapper.querySelector('#stackWrap');
const crashBox = wrapper.querySelector('#crashBox');
const stopBox = wrapper.querySelector('#stopBox');
const caption = wrapper.querySelector('#caption');
let isWith = false;
let timer = null;
let idx = -1;
let sequence = [];
function tone() { return isWith ? 'with-tone' : 'without-tone'; }
function renderCode() {
if (isWith) {
codeBlock.innerHTML =
`<span class="kw">function</span> countDown(fromNumber) {
console.log(fromNumber);
<span class="kw">let</span> nextNumber = fromNumber - 1;
<span class="kw">if</span> (nextNumber > 0) {
<span class="ok-tok">countDown(nextNumber);</span>
}
}
countDown(3);`;
} else {
codeBlock.innerHTML =
`<span class="kw">function</span> countDown(fromNumber) {
console.log(fromNumber);
<span class="bad-tok">countDown(fromNumber - 1);</span> <span class="comment">// no base case!</span>
}
countDown(3);`;
}
}
function buildSequence() {
sequence = [];
if (isWith) {
[3, 2, 1].forEach(n => sequence.push({ n }));
sequence.push({ stop: true });
} else {
[3, 2, 1, 0, -1, -2, -3].forEach(n => sequence.push({ n }));
sequence.push({ crash: true });
}
}
function resetVisuals() {
stackWrap.innerHTML = '';
crashBox.classList.remove('is-visible', 'shake');
stopBox.classList.remove('is-visible');
playBtn.className = `ctrl-btn primary ${tone()}`;
playBtn.textContent = '▶ Run countDown(3)';
}
function switchMode(nextIsWith) {
isWith = nextIsWith;
switchEl.classList.toggle('is-on', isWith);
labelWith.classList.toggle('active', isWith);
labelWithout.classList.toggle('active', !isWith);
stop();
idx = -1;
renderCode();
buildSequence();
resetVisuals();
caption.textContent = isWith
? 'Press Run to see recursion stop cleanly.'
: 'Press Run to see what happens without a stopping condition.';
}
function addFrame(n) {
const depth = stackWrap.children.length;
const frame = document.createElement('div');
frame.className = `frame ${tone()}`;
frame.style.marginLeft = `${Math.min(depth, 7) * 0.9}rem`;
frame.innerHTML = `<span class="frame-call">countDown(${n})</span><span class="frame-print">prints <strong class="${tone()}">${n}</strong></span>`;
stackWrap.appendChild(frame);
requestAnimationFrame(() => frame.classList.add('is-in'));
}
function advance() {
idx++;
if (idx >= sequence.length) { stop(); return; }
const step = sequence[idx];
if (step.crash) {
crashBox.classList.add('is-visible');
requestAnimationFrame(() => crashBox.classList.add('shake'));
caption.innerHTML = `<strong>No base case</strong> — it never stops calling itself until the stack runs out`;
stop();
return;
}
if (step.stop) {
stopBox.classList.add('is-visible');
caption.innerHTML = `<strong>nextNumber is 0</strong> — the if-condition is false, so it doesn't call again`;
stop();
return;
}
addFrame(step.n);
caption.innerHTML = `<strong>countDown(${step.n})</strong> prints ${step.n}${
isWith ? (step.n > 1 ? `, calls countDown(${step.n - 1})` : '') : `, calls countDown(${step.n - 1})`
}`;
if (idx >= sequence.length - 1) stop();
}
function stop() {
clearInterval(timer);
timer = null;
playBtn.textContent = `▶ Run countDown(3)`;
}
function play() {
if (timer) { stop(); return; }
if (idx >= sequence.length - 1) {
idx = -1;
resetVisuals();
}
playBtn.textContent = '⏸ Pause';
timer = setInterval(advance, 550);
advance();
}
playBtn.addEventListener('click', play);
restartBtn.addEventListener('click', () => { stop(); idx = -1; resetVisuals(); caption.textContent = isWith ? 'Press Run to see recursion stop cleanly.' : 'Press Run to see what happens without a stopping condition.'; });
switchEl.addEventListener('click', () => switchMode(!isWith));
labelWithout.addEventListener('click', () => switchMode(false));
labelWith.addEventListener('click', () => switchMode(true));
switchMode(false);
})();
</script>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});✅ Example 2: Sum of First n Natural Numbers Using Recursion
Calculate the sum of numbers from 1 to n.
Formula (Recursive)
sum(n) = n + sum(n - 1)<div class="infographic-recursion">
<style>
.infographic-recursion {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--line: #ddd6c9;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--ok: #1a7f4e;
--ok-soft: #e3f5ec;
--warn: #b45309;
--warn-soft: #fdf0dc;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', -apple-system, sans-serif;
--mono-font: 'IBM Plex Mono', 'Courier New', monospace;
margin: 0 auto;
box-sizing: border-box;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.infographic-recursion[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--line: #2c313d;
--accent: #5b8def;
--accent-soft:#202942;
--ok: #5cd6a0;
--ok-soft: #16342a;
--warn: #e0a458;
--warn-soft: #3a2c14;
}
.infographic-recursion * { box-sizing: border-box; }
.infographic-recursion .ig-inner { padding: 1.75rem clamp(1.25rem, 5vw, 3rem) 2rem; }
.infographic-recursion .ig-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 4.5vw, 1.9rem);
font-weight: 600; text-align: center; margin: 0 0 1.25rem;
}
.infographic-recursion .code-block {
font-family: var(--mono-font); font-size: 0.82rem; line-height: 1.7;
background: var(--paper-alt); border: 1px solid var(--line); border-radius: 6px;
padding: 0.9rem 1rem; white-space: pre; margin-bottom: 1.5rem;
}
.infographic-recursion .code-block .kw { color: var(--accent); }
.infographic-recursion .code-block .base { color: var(--ok); font-weight: 700; }
.infographic-recursion .code-block .rec { color: var(--warn); font-weight: 700; }
.infographic-recursion .code-block .comment { color: var(--ink-soft); }
.infographic-recursion .controls { display: flex; align-items: center; justify-content: center; gap: 0.6rem; flex-wrap: wrap; padding-top: 1.4rem; }
.infographic-recursion .n-wrap { position: relative; display: flex; align-items: center; height: 2.4rem; }
.infographic-recursion .n-tag {
position: absolute; top: -1.3rem; left: 50%; transform: translateX(-50%);
font-size: 0.65rem; font-family: var(--mono-font); color: var(--accent);
background: var(--accent-soft); padding: 0.1rem 0.4rem; border-radius: 4px; white-space: nowrap;
}
.infographic-recursion .n-input {
width: 4rem; height: 2.4rem; font-family: var(--mono-font); font-size: 1rem; font-weight: 700;
text-align: center; border: 2px solid var(--accent); background: var(--accent-soft); color: var(--accent);
border-radius: 6px; outline: none;
}
.infographic-recursion .ctrl-btn {
font: inherit; font-size: 0.82rem; font-weight: 600; border: 1px solid var(--line);
background: var(--paper-alt); color: var(--ink); padding: 0.45rem 1rem;
border-radius: 999px; cursor: pointer;
}
.infographic-recursion .ctrl-btn:hover { border-color: var(--accent); color: var(--accent); }
.infographic-recursion .ctrl-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.infographic-recursion .stack-wrap { margin-top: 1.75rem; min-height: 15rem; }
.infographic-recursion .frame {
display: flex; align-items: center; gap: 0.6rem;
font-family: var(--mono-font); font-size: 0.8rem;
border: 1px solid var(--line); background: var(--paper-alt);
padding: 0.5rem 0.7rem; border-radius: 6px; margin-bottom: 0.4rem;
opacity: 0; transform: translateY(-8px) scale(0.95);
transition: opacity 0.3s ease, transform 0.3s ease, background 0.3s ease, border-color 0.3s ease;
}
.infographic-recursion .frame.is-in { opacity: 1; transform: translateY(0) scale(1); }
.infographic-recursion .frame.is-active { border-color: var(--accent); box-shadow: 0 3px 8px rgba(0,0,0,0.1); }
.infographic-recursion .frame.is-base { border-color: var(--ok); background: var(--ok-soft); }
.infographic-recursion .frame.is-returned { border-color: var(--ok); background: var(--ok-soft); opacity: 0.55; }
.infographic-recursion .frame-call { font-weight: 700; color: var(--ink); white-space: nowrap; }
.infographic-recursion .frame-status { color: var(--ink-soft); font-size: 0.75rem; }
.infographic-recursion .frame-status strong { color: var(--ok); }
.infographic-recursion .result-line {
text-align: center; font-family: var(--mono-font); font-size: 0.95rem; font-weight: 700;
margin-top: 1rem; padding: 0.6rem; border-radius: 6px;
background: var(--ok-soft); color: var(--ok); border: 1px solid var(--ok);
opacity: 0; transform: scale(0.9); transition: opacity 0.3s ease, transform 0.3s ease;
}
.infographic-recursion .result-line.is-visible { opacity: 1; transform: scale(1); }
.infographic-recursion .caption {
text-align: center; font-size: 0.85rem; color: var(--ink-soft); margin-top: 1rem; min-height: 1.2em;
}
.infographic-recursion .caption strong { color: var(--ink); font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-recursion *, .infographic-recursion *::before, .infographic-recursion *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="ig-inner">
<h2 class="ig-title">Sum of First n Numbers</h2>
<div class="code-block"><span class="kw">function</span> sum(n) {
<span class="kw">if</span> (n <= 1) <span class="kw">return</span> <span class="base">n</span>; <span class="comment">// base case</span>
<span class="kw">return</span> <span class="rec">n + sum(n - 1)</span>; <span class="comment">// recursive case</span>
}</div>
<div class="controls">
<div class="n-wrap">
<span class="n-tag">n</span>
<input type="number" class="n-input" id="nInput" value="4" min="1" max="6">
</div>
<button class="ctrl-btn primary" id="playBtn">▶ Play</button>
<button class="ctrl-btn" id="restartBtn">⟲ Restart</button>
</div>
<div class="stack-wrap" id="stackWrap"></div>
<div class="result-line" id="resultLine"></div>
<p class="caption" id="caption">Set n, then press Play to watch the call stack grow and unwind.</p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-recursion')
|| document.querySelector('.infographic-recursion');
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 nInput = wrapper.querySelector('#nInput');
const playBtn = wrapper.querySelector('#playBtn');
const restartBtn = wrapper.querySelector('#restartBtn');
const stackWrap = wrapper.querySelector('#stackWrap');
const resultLine = wrapper.querySelector('#resultLine');
const caption = wrapper.querySelector('#caption');
let frameEls = [];
let timer = null;
let steps = [];
let idx = -1;
function buildSteps(n) {
steps = [];
// Push phase
for (let i = n; i >= 1; i--) {
const isBase = i === 1;
steps.push({
type: 'push', depth: n - i, n: i, isBase,
status: isBase ? 'n = 1 → base case, return 1' : `n = ${i}, calls sum(${i - 1})`
});
}
// Unwind phase
let value = 1;
for (let i = 1; i <= n; i++) {
const depth = n - i;
if (i === 1) {
steps.push({ type: 'unwind', depth, n: i, text: 'returns 1', value: 1 });
} else {
const newValue = i + value;
steps.push({ type: 'unwind', depth, n: i, text: `${i} + ${value} = ${newValue}`, value: newValue });
value = newValue;
}
}
steps.push({ type: 'done', n, value });
}
function reset() {
stackWrap.innerHTML = '';
frameEls = [];
resultLine.classList.remove('is-visible');
resultLine.textContent = '';
}
function applyStep(step) {
if (step.type === 'push') {
const frame = document.createElement('div');
frame.className = 'frame is-active' + (step.isBase ? ' is-base' : '');
frame.style.marginLeft = `${step.depth * 1.1}rem`;
frame.innerHTML = `<span class="frame-call">sum(${step.n})</span><span class="frame-status">${step.status}</span>`;
stackWrap.appendChild(frame);
frameEls[step.depth] = frame;
requestAnimationFrame(() => frame.classList.add('is-in'));
frameEls.forEach(f => f && f.classList.remove('is-active'));
frame.classList.add('is-active');
caption.innerHTML = step.isBase
? `<strong>n = 1</strong> hits the base case — time to unwind`
: `<strong>n = ${step.n}</strong> calls <strong>sum(${step.n - 1})</strong>`;
}
if (step.type === 'unwind') {
const frame = frameEls[step.depth];
frame.classList.remove('is-active');
frame.classList.add('is-returned');
frame.querySelector('.frame-status').innerHTML = `<strong>${step.text}</strong>`;
caption.innerHTML = `<strong>sum(${step.n})</strong> returns <strong>${step.value}</strong>`;
}
if (step.type === 'done') {
resultLine.textContent = `sum(${step.n}) = ${step.value}`;
resultLine.classList.add('is-visible');
caption.innerHTML = `Done — unwound all the way back to <strong>sum(${step.n})</strong>`;
}
}
function stop() {
clearInterval(timer);
timer = null;
playBtn.textContent = '▶ Play';
}
function advance() {
idx++;
if (idx >= steps.length) { stop(); return; }
applyStep(steps[idx]);
if (idx >= steps.length - 1) stop();
}
function play() {
if (timer) { stop(); return; }
if (idx >= steps.length - 1 || idx === -1) {
restart(false);
}
playBtn.textContent = '⏸ Pause';
timer = setInterval(advance, 700);
advance();
}
function restart(clearCaption = true) {
stop();
idx = -1;
reset();
const n = Math.max(1, Math.min(6, Number(nInput.value) || 4));
nInput.value = n;
buildSteps(n);
if (clearCaption) caption.textContent = 'Set n, then press Play to watch the call stack grow and unwind.';
}
playBtn.addEventListener('click', play);
restartBtn.addEventListener('click', () => restart(true));
nInput.addEventListener('change', () => restart(true));
restart(true);
})();
</script>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});