In JavaScript, callbacks play an essential role in handling asynchronous tasks like reading files, making API requests, and executing code after certain events.
If you’ve ever heard the phrase "I will call back later!", that’s exactly how callbacks work.
A callback function is a function that is passed as an argument to another function and executed later.
A function can accept another function as a parameter.
Callbacks allow one function to call another at a later time.
A callback function can execute after another function has finished.
function greet(name, callback) {
console.log("Hello, " + name);
callback();
}
function sayBye() {
console.log("Goodbye!");
}
greet("Ajay", sayBye);Hello, Ajay
Goodbye!Here, sayBye( ) is passed as a callback to greet( ), which executes after the greeting.
In simple words: A callback is a “function that will be called back later.”
<div class="infographic-callback">
<style>
.infographic-callback {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--amber: #a4720a;
--amber-soft: #faf1de;
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-callback[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;
--amber: #d9a94a;
--amber-soft: #2c2413;
}
.infographic-callback * { box-sizing: border-box; }
.infographic-callback h1, .infographic-callback h2 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-callback p { margin: 0; }
.infographic-callback button { font-family: inherit; }
.cb-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.cb-hero { text-align: center; margin-bottom: 1.6rem; }
.cb-hero h1 { font-size: clamp(1.4rem, 4.5vw, 1.95rem); }
.cb-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.88rem; }
.cb-section { padding: 0.4rem 0 0.5rem; }
.cb-section h2 { font-size: 1.02rem; text-align: center; margin-bottom: 1rem; }
/* handoff */
.cb-handoff { display: flex; align-items: center; justify-content: center; gap: 1.2rem; flex-wrap: wrap; position: relative; min-height: 90px; }
.cb-fnbox { width: 150px; text-align: center; border: 1.5px solid var(--accent); background: var(--accent-soft); padding: 0.55rem 0.6rem; transition: opacity 0.4s ease, transform 0.4s ease; }
.cb-fnbox .cb-name { font-family: 'IBM Plex Mono', monospace; font-weight: 700; font-size: 0.76rem; color: var(--accent); }
.cb-fnbox .cb-sub { font-size: 0.6rem; color: var(--ink-soft); margin-top: 0.1rem; }
.cb-fnbox.is-taken { opacity: 0.25; }
.cb-arrow { font-size: 1.3rem; color: var(--line); transition: color 0.4s ease; }
.cb-arrow.is-lit { color: var(--accent); }
.cb-outer { width: 230px; text-align: center; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.55rem 0.6rem; }
.cb-outer .cb-name { font-family: 'IBM Plex Mono', monospace; font-weight: 700; font-size: 0.76rem; }
.cb-outer .cb-slot { margin-top: 0.4rem; border: 1.5px dashed var(--line); padding: 0.3rem 0.4rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--ink-soft); transition: border-color 0.4s ease, background 0.4s ease, color 0.4s ease; }
.cb-outer .cb-slot.is-filled { border-style: solid; border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 700; }
.cb-handoff-play { display: block; margin: 1.6rem auto 0; border: none; background: var(--accent); color: #fff; padding: 0.5rem 1.2rem; font-size: 0.8rem; font-weight: 600; cursor: pointer; }
.cb-handoff-play:active { transform: scale(0.96); }
.cb-handoff-note { text-align: center; font-size: 0.78rem; color: var(--ink-soft); padding-top: 1rem; }
.cb-handoff-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-callback *, .infographic-callback *::before, .infographic-callback *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="cb-wrap">
<div class="cb-hero">
<h1>Callback functions</h1>
<p>A function is just a value — you can hand it to another function and let that one decide when to run it.</p>
</div>
<div class="cb-section">
<h2>The handoff</h2>
<div class="cb-handoff">
<div class="cb-fnbox" id="cbFnBox">
<div class="cb-name">function greet(name)</div>
<div class="cb-sub">console.log("Hi " + name)</div>
</div>
<div class="cb-arrow" id="cbArrow">→</div>
<div class="cb-outer">
<div class="cb-name">processUser(name, callback)</div>
<div class="cb-slot" id="cbSlot">callback: —</div>
</div>
</div>
<button class="cb-handoff-play" id="cbHandoffPlay" type="button">processUser("Alice", greet)</button>
<p class="cb-handoff-note">greet isn't called yet here — it's just <strong>passed in</strong>, sitting in the callback slot until processUser decides to use it.</p>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-callback')
|| document.querySelector('.infographic-callback');
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');
/* handoff demo */
const fnBox = wrapper.querySelector('#cbFnBox');
const arrow = wrapper.querySelector('#cbArrow');
const slot = wrapper.querySelector('#cbSlot');
wrapper.querySelector('#cbHandoffPlay').addEventListener('click', () => {
fnBox.classList.remove('is-taken');
arrow.classList.remove('is-lit');
slot.classList.remove('is-filled');
slot.textContent = 'callback: —';
void fnBox.offsetWidth;
setTimeout(() => { arrow.classList.add('is-lit'); }, 60);
setTimeout(() => {
fnBox.classList.add('is-taken');
slot.classList.add('is-filled');
slot.textContent = 'callback: greet';
}, 420);
});
})();
</script>💮 Types of Callback
There are two types of callbacks: synchronous and asynchronous callbacks.
🔸 Synchronous callbacks
A synchronous callback is executed during the execution of the high-order function that uses the callback. The
isOddandisEvenare examples of synchronous callbacks because they execute during the execution of thefilter()function.
Example :
function isOdd(num) {
return num % 2 !== 0;
}
function isEven(num) {
return num % 2 === 0
}
const numbers = [1, 2, 3, 4, 5];
const oddNumbers = numbers.filter(isOdd);
const evenNumbers = numbers.filter(isEven);
console.log("Odd Numbers:", oddNumbers);
console.log("Even Numbers:", evenNumbers);filter()is a synchronous higher-order function.It accepts
isOddandisEvenas callbacks.Both callbacks run immediately for each element in the array.
No waiting or async behavior — everything completes before the next line runs.
oddNumbersandevenNumbersare ready right afterfilter()finishes.
Odd Numbers: [1,4,5]
Even Numbers: [2,4]🔹Asynchronous callbacks
An asynchronous callback is executed after the execution of the high-order function that uses the callback.
Asynchronicity means that if JavaScript has to wait for an operation to complete, it will execute the rest of the code while waiting.
Note that JavaScript is a single-threaded programming language. It carries asynchronous operations via the callback queue and event loop.
Example :
console.log("Start");
setTimeout(() => {
console.log("Callback Executed (Async)");
}, 2000);
console.log("End");"Start" prints immediately.
setTimeout()schedules the callback to run after 2 seconds, but it does not block the code.JavaScript moves on and prints "End" immediately.
After 2 seconds, the callback is taken from the task queue and executed →
"Callback Executed (Async)" appears.
✔️ Why does this happen?
setTimeout()uses the Web APIs (browser environment).The callback is placed in the callback queue and waits.
JavaScript continues executing the rest of the code (because it's non-blocking).
Once the main call stack is empty, the event loop pushes the callback to run.
Start
End
Cllaback Executed (Async)<div class="infographic-syncasync">
<style>
.infographic-syncasync {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--amber: #a4720a;
--amber-soft: #faf1de;
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-syncasync[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;
--amber: #d9a94a;
--amber-soft: #2c2413;
}
.infographic-syncasync * { box-sizing: border-box; }
.infographic-syncasync h1, .infographic-syncasync h2 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-syncasync p { margin: 0; }
.infographic-syncasync button { font-family: inherit; }
.sa-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.sa-hero { text-align: center; margin-bottom: 1.6rem; }
.sa-hero h1 { font-size: clamp(1.4rem, 4.5vw, 1.95rem); }
.sa-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.88rem; }
.sa-lanes { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--line); border: 1px solid var(--line); }
@media (max-width: 680px) { .sa-lanes { grid-template-columns: 1fr; } }
.sa-lane { background: var(--paper); padding: 1.1rem 1rem 1.4rem; }
.sa-lane-head { text-align: center; margin-bottom: 1rem; }
.sa-lane-head h2 { font-size: 1rem; }
.sa-lane-head code { display: block; font-family: 'IBM Plex Mono', monospace; font-size: 0.65rem; color: var(--ink-soft); margin-top: 0.4rem; white-space: pre-wrap; text-align: left; }
/* timeline bars */
.sa-timeline { display: flex; flex-direction: column; gap: 0.55rem; margin-top: 1rem; }
.sa-task { display: flex; align-items: center; gap: 0.5rem; }
.sa-task-label { width: 58px; flex-shrink: 0; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; color: var(--ink-soft); }
.sa-task-track { flex: 1; height: 16px; background: var(--paper-alt); border: 1px solid var(--line); position: relative; overflow: hidden; }
.sa-task-bar { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; background: var(--accent); transition: width linear; }
.sa-task.done .sa-task-bar { background: var(--good); }
.sa-task-check { width: 16px; flex-shrink: 0; text-align: center; font-size: 0.7rem; color: var(--good); opacity: 0; transition: opacity 0.2s ease; }
.sa-task.done .sa-task-check { opacity: 1; }
.sa-play { display: block; margin: 1.3rem auto 0; border: none; background: var(--accent); color: #fff; padding: 0.5rem 1.2rem; font-size: 0.8rem; font-weight: 600; cursor: pointer; }
.sa-play:active { transform: scale(0.96); }
.sa-play:disabled { opacity: 0.45; cursor: default; }
.sa-console {
margin: 1rem auto 0; max-width: 280px; text-align: left;
border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.5rem 0.75rem;
font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; min-height: 4.4em;
}
.sa-console .sa-cline { opacity: 0; transition: opacity 0.25s ease; color: var(--good); }
.sa-console .sa-cline.is-shown { opacity: 1; }
.sa-total { text-align: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.74rem; color: var(--ink-soft); margin-top: 0.8rem; }
.sa-total strong { color: var(--ink); opacity: 0; transition: opacity 0.3s ease; }
.sa-total strong.is-shown { opacity: 1; }
.sa-note { text-align: center; font-size: 0.8rem; color: var(--ink-soft); margin-top: 1.6rem; padding-top: 1.1rem; border-top: 1px dashed var(--line); }
.sa-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-syncasync *, .infographic-syncasync *::before, .infographic-syncasync *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="sa-wrap">
<div class="sa-hero">
<h1>Synchronous vs Asynchronous</h1>
<p>Same two tasks, run two different ways. Watch the clock.</p>
</div>
<div class="sa-lanes">
<!-- Synchronous -->
<div class="sa-lane">
<div class="sa-lane-head">
<h2>Synchronous — one waits for the other</h2>
<code>task("A", 2s); // blocks
task("B", 1s); // blocks
console.log("Done");</code>
</div>
<div class="sa-timeline">
<div class="sa-task" id="syncA">
<div class="sa-task-label">A (2s)</div>
<div class="sa-task-track"><div class="sa-task-bar" id="syncBarA"></div></div>
<div class="sa-task-check">✓</div>
</div>
<div class="sa-task" id="syncB">
<div class="sa-task-label">B (1s)</div>
<div class="sa-task-track"><div class="sa-task-bar" id="syncBarB"></div></div>
<div class="sa-task-check">✓</div>
</div>
</div>
<button class="sa-play" data-lane="sync" type="button">Run</button>
<div class="sa-console">
<div class="sa-cline" id="syncLog1">A</div>
<div class="sa-cline" id="syncLog2">B</div>
<div class="sa-cline" id="syncLog3">Done</div>
</div>
<div class="sa-total">total: <strong id="syncTotal">~3s</strong></div>
</div>
<!-- Asynchronous -->
<div class="sa-lane">
<div class="sa-lane-head">
<h2>Asynchronous — both run at once</h2>
<code>setTimeout(() => log("A"), 2s);
setTimeout(() => log("B"), 1s);
console.log("Done");</code>
</div>
<div class="sa-timeline">
<div class="sa-task" id="asyncA">
<div class="sa-task-label">A (2s)</div>
<div class="sa-task-track"><div class="sa-task-bar" id="asyncBarA"></div></div>
<div class="sa-task-check">✓</div>
</div>
<div class="sa-task" id="asyncB">
<div class="sa-task-label">B (1s)</div>
<div class="sa-task-track"><div class="sa-task-bar" id="asyncBarB"></div></div>
<div class="sa-task-check">✓</div>
</div>
</div>
<button class="sa-play" data-lane="async" type="button">Run</button>
<div class="sa-console">
<div class="sa-cline" id="asyncLog1">Done</div>
<div class="sa-cline" id="asyncLog2">B</div>
<div class="sa-cline" id="asyncLog3">A</div>
</div>
<div class="sa-total">total: <strong id="asyncTotal">~2s</strong></div>
</div>
</div>
<p class="sa-note">Sync work <strong>blocks</strong> — nothing else happens until it's done. Async work runs <strong>in the background</strong>, so the total wait is the longest task, not the sum of them.</p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-syncasync')
|| document.querySelector('.infographic-syncasync');
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 DUR_A = 1000; // demo ms standing in for 2s
const DUR_B = 500; // demo ms standing in for 1s
function resetBar(bar, taskEl) {
bar.style.transition = 'none';
bar.style.width = '0%';
taskEl.classList.remove('done');
void bar.offsetWidth;
}
function growBar(bar, ms) {
bar.style.transition = 'width ' + ms + 'ms linear';
requestAnimationFrame(() => { bar.style.width = '100%'; });
}
function playSync() {
const btn = wrapper.querySelector('.sa-play[data-lane="sync"]');
const barA = wrapper.querySelector('#syncBarA');
const barB = wrapper.querySelector('#syncBarB');
const taskA = wrapper.querySelector('#syncA');
const taskB = wrapper.querySelector('#syncB');
const log1 = wrapper.querySelector('#syncLog1');
const log2 = wrapper.querySelector('#syncLog2');
const log3 = wrapper.querySelector('#syncLog3');
const total = wrapper.querySelector('#syncTotal');
btn.disabled = true;
resetBar(barA, taskA);
resetBar(barB, taskB);
[log1, log2, log3].forEach(l => l.classList.remove('is-shown'));
total.classList.remove('is-shown');
growBar(barA, DUR_A);
setTimeout(() => { taskA.classList.add('done'); log1.classList.add('is-shown'); }, DUR_A);
setTimeout(() => {
growBar(barB, DUR_B);
}, DUR_A);
setTimeout(() => { taskB.classList.add('done'); log2.classList.add('is-shown'); }, DUR_A + DUR_B);
setTimeout(() => {
log3.classList.add('is-shown');
total.classList.add('is-shown');
btn.disabled = false;
}, DUR_A + DUR_B + 150);
}
function playAsync() {
const btn = wrapper.querySelector('.sa-play[data-lane="async"]');
const barA = wrapper.querySelector('#asyncBarA');
const barB = wrapper.querySelector('#asyncBarB');
const taskA = wrapper.querySelector('#asyncA');
const taskB = wrapper.querySelector('#asyncB');
const log1 = wrapper.querySelector('#asyncLog1');
const log2 = wrapper.querySelector('#asyncLog2');
const log3 = wrapper.querySelector('#asyncLog3');
const total = wrapper.querySelector('#asyncTotal');
btn.disabled = true;
resetBar(barA, taskA);
resetBar(barB, taskB);
[log1, log2, log3].forEach(l => l.classList.remove('is-shown'));
total.classList.remove('is-shown');
growBar(barA, DUR_A);
growBar(barB, DUR_B);
log1.classList.add('is-shown');
setTimeout(() => { taskB.classList.add('done'); log2.classList.add('is-shown'); }, DUR_B);
setTimeout(() => {
taskA.classList.add('done');
log3.classList.add('is-shown');
total.classList.add('is-shown');
btn.disabled = false;
}, DUR_A);
}
wrapper.querySelectorAll('.sa-play').forEach(btn => {
btn.addEventListener('click', () => {
if (btn.dataset.lane === 'sync') playSync();
else playAsync();
});
});
})();
</script>✅ Key Difference Recap:
Feature | Synchronous Callback | Asynchronous Callback |
|---|---|---|
Execution Time | Immediately during function execution | After the current execution is finished |
Blocking | Yes | No |
Use Cases |
|
|
🔷Where Are Callbacks Used?
Callbacks are widely used in
API requests (fetching data)
Reading files (Node.js file system)
Event listeners (clicks, keyboard inputs)
Database queries (retrieving data)
1. Callbacks in Functions Handling Operations
When a function needs to execute different behaviors based on input, callbacks make the function flexible.
function calc(a, b, callback) {
return callback(a, b);
}
function add(x, y) {
return x + y;
}
function mul(x, y) {
return x * y;
}
console.log(calc(5, 3, add));
console.log(calc(5, 3, mul));8
15calculate( ) receives two numbers and a function (add or multiply).
The passed function is executed inside calculate( ).
2. Callbacks in Event Listeners
JavaScript is event-driven, and callbacks handle user interactions like clicks and key presses.
document.getElementById("myButton").addEventListener("click", **function** () {
console.log("Button clicked!");
});Here, the anonymous function is a callback that runs when the button is clicked.
3. Callbacks in API Calls (Fetching Data)
Callbacks are useful when retrieving data from APIs.
function getData(callback) {
fetch("<https://jsonplaceholder.typicode.com/todos/1>")
.then(res => res.json())
.then(data => callback(data));
}
function showData(data) {
console.log("Fetched Data:", data);
}
getData(showData);fetchData( ) gets data from an API and passes it to showData( ) for processing.
🔸 Features of JavaScript Callbacks
Asynchronous Execution: Handle async tasks like API calls, timers, and events without blocking execution.
Code Reusability: Write modular code by passing different callbacks for different behaviors.
Event-Driven Programming: Enable event-based execution (e.g., handling clicks, keypresses).
Error Handling: Pass errors to callbacks for better control in async operations.
Non-Blocking Execution: Keep the main thread free by running long tasks asynchronously.
🟥 When to Avoid Callbacks?
Code becomes nested and unreadable (use Promises or async/await).
You need error handling in asynchronous operations (Promises are better).