✅ What is Synchronous JavaScript?
In synchronous programming, code runs one line at a time, and each line waits for the previous one to finish.
Example: In this example, we have shown the synchronous nature of JavaScript.
console.log("Hi");
console.log("Geek");
console.log("How are you?");Hi
Geek
How are you?Key Points
Code executes in order, line-by-line.
Each task blocks the next one until it finishes.
Slow operations (like file read or API call) can freeze the program/UI.
That's why JavaScript uses asynchronous techniques (callbacks, promises, async/await) for long tasks.
✅ What is Asynchronous JavaScript?
Asynchronous programming lets tasks run without waiting for each other. A task can start, and while it's still running, the program can continue doing other work. This non-blocking behavior keeps apps fast and responsive.
Example: In this example, we have shown the Asynchronous nature of JavaScript.
console.log("Hi");
setTimeout(() => {
console.log("Geek");
}, 2000);
console.log("End");Hi
End
Geek➡️ Understanding Asynchronous JavaScript with setTimeout
Order of Execution
"Hi"is logged first.JavaScript does not wait for
setTimeout, so"End"prints next.The
setTimeoutmessage prints after the delay.
Why It Doesn’t Block
JavaScript is single-threaded but uses Web APIs for async tasks.
setTimeoutis handed off to the Web API while JS keeps running other code.
Behind the Scenes
setTimeoutgoes to the Web API.After the timer ends, its callback moves to the callback queue.
The event loop adds it to the call stack when it's free.
No Blocking
The rest of the code runs normally;
setTimeoutdoes not pause the program.
❓ How Asynchronous JavaScript Works
JavaScript is single-threaded, but it can still handle tasks without blocking.
This happens because of Call Stack, Web APIs, Callback Queue, and the Event Loop.
1. Call Stack
This is where functions run.
JavaScript executes code one function at a time.
Synchronous code goes here directly.
2. Web APIs (Browser Features)
Some functions (like
setTimeout, fetch requests, DOM events) don’t run on the call stack.They are handled by the browser’s Web APIs.
These APIs work in the background without blocking JavaScript.
3. Callback Queue
When a Web API finishes its task (timer ends, data arrives, event triggered),
it puts the callback function into the callback queue.
4. Event Loop
Continuously checks:
Is the call stack empty?
If yes → moves callbacks from the queue to the stack.
This is why async tasks run after synchronous code.
<div class="infographic-eventloop">
<style>
.infographic-eventloop {
--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-eventloop[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-eventloop * { box-sizing: border-box; }
.infographic-eventloop h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-eventloop p { margin: 0; }
.infographic-eventloop button { font-family: inherit; }
.el-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.el-hero { text-align: center; margin-bottom: 1.3rem; }
.el-hero h1 { font-size: clamp(1.35rem, 4.5vw, 1.8rem); }
.el-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.85rem; }
.el-code { border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.6rem 0.85rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; line-height: 1.8; margin-bottom: 1.3rem; }
.el-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); }
.el-code .ln .num { opacity: 0.5; width: 1.1em; flex-shrink: 0; }
.el-code .ln.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.el-board { display: flex; flex-direction: column; gap: 0.7rem; align-items: center; }
.el-row { display: flex; gap: 0.7rem; align-items: stretch; justify-content: center; flex-wrap: wrap; }
.el-panel { border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.5rem 0.6rem; text-align: center; transition: border-color 0.3s ease; }
.el-panel .el-plabel { font-family: 'Fraunces', Georgia, serif; font-size: 0.8rem; margin-bottom: 0.35rem; }
.el-stack { width: 150px; }
.el-stack .el-slot { height: 40px; border: 1.5px dashed var(--line); display: flex; align-items: center; justify-content: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; color: var(--ink-soft); transition: border-color 0.3s ease, background 0.3s ease, color 0.3s ease; }
.el-stack .el-slot.is-active { border-style: solid; border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 700; }
.el-webapi { width: 150px; }
.el-webapi .el-slot { height: 40px; border: 1.5px dashed var(--line); display: flex; align-items: center; justify-content: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem; color: var(--ink-soft); transition: border-color 0.3s ease, background 0.3s ease, color 0.3s ease; gap: 0.25rem; }
.el-webapi .el-slot.is-active { border-style: solid; border-color: var(--amber); background: var(--amber-soft); color: var(--amber); font-weight: 700; }
.el-webapi .el-slot svg { width: 12px; height: 12px; }
.el-loop { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; padding: 0.3rem; }
.el-loop svg { width: 24px; height: 24px; stroke: var(--ink-soft); transition: stroke 0.3s ease; }
.el-loop .el-plabel { margin-bottom: 0.1rem; }
.el-loop.is-checking svg { stroke: var(--accent); animation: elSpin 0.7s ease; }
@keyframes elSpin { to { transform: rotate(360deg); } }
.el-loop-note { font-size: 0.58rem; color: var(--ink-soft); max-width: 90px; }
.el-queue { width: 100%; max-width: 340px; }
.el-queue .el-qrow { display: flex; gap: 0.5rem; justify-content: center; min-height: 40px; align-items: center; }
.el-qitem { border: 1.5px dashed var(--line); padding: 0.3rem 0.6rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.64rem; color: var(--ink-soft); opacity: 0; transform: scale(0.9); transition: opacity 0.35s ease, transform 0.35s ease, border-color 0.35s ease, color 0.35s ease, background 0.35s ease; }
.el-qitem.is-active { opacity: 1; transform: scale(1); border-style: solid; border-color: var(--good); background: var(--good-soft); color: var(--good); font-weight: 700; }
.el-qempty { font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem; color: var(--ink-soft); opacity: 0.6; }
.el-console {
width: 100%; max-width: 340px; text-align: left;
border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.5rem 0.8rem;
font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; min-height: 2.2em;
}
.el-console .el-clabel { font-size: 0.6rem; color: var(--ink-soft); margin-bottom: 0.2rem; }
.el-console .el-line { opacity: 0; transition: opacity 0.3s ease; }
.el-console .el-line.is-shown { opacity: 1; color: var(--good); }
.el-controls { display: flex; align-items: center; justify-content: center; gap: 0.8rem; margin-top: 1.3rem; }
.el-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; }
.el-nav:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.el-nav:disabled { opacity: 0.35; cursor: default; }
.el-step-count { font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; color: var(--ink-soft); min-width: 70px; text-align: center; }
.el-caption { text-align: center; font-size: 0.84rem; color: var(--ink-soft); padding-top: 1.2rem; min-height: 2.6em; width: 100%; }
.el-caption strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-eventloop *, .infographic-eventloop *::before, .infographic-eventloop *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="el-wrap">
<div class="el-hero">
<h1>How the event loop actually runs this</h1>
<p>One thread, four moving parts. Step through it.</p>
</div>
<div class="el-code" id="elCode">
<div class="ln" data-line="1"><span class="num">1</span><span>console.log("1");</span></div>
<div class="ln" data-line="2"><span class="num">2</span><span>setTimeout(() => console.log("2"), 0);</span></div>
<div class="ln" data-line="3"><span class="num">3</span><span>console.log("3");</span></div>
</div>
<div class="el-board">
<div class="el-row">
<div class="el-panel el-stack">
<div class="el-plabel">Call Stack</div>
<div class="el-slot" id="elStackSlot">empty</div>
</div>
<div class="el-panel el-webapi">
<div class="el-plabel">Web APIs</div>
<div class="el-slot" id="elWebSlot">idle</div>
</div>
</div>
<div class="el-loop" id="elLoop">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>
<div class="el-plabel">Event Loop</div>
<div class="el-loop-note" id="elLoopNote">is the stack empty?</div>
</div>
<div class="el-panel el-queue">
<div class="el-plabel">Callback Queue</div>
<div class="el-qrow">
<div class="el-qempty" id="elQEmpty">empty</div>
<div class="el-qitem" id="elQItem">() => console.log("2")</div>
</div>
</div>
<div class="el-console">
<div class="el-clabel">console</div>
<div class="el-line" id="cLine1">1</div>
<div class="el-line" id="cLine3">3</div>
<div class="el-line" id="cLine2">2</div>
</div>
</div>
<div class="el-controls">
<button class="el-nav" id="elPrev" type="button">◂ Prev</button>
<span class="el-step-count" id="elCount">Step 1 of 7</span>
<button class="el-nav" id="elNext" type="button">Next ▸</button>
</div>
<p class="el-caption" id="elCaption"></p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-eventloop')
|| document.querySelector('.infographic-eventloop');
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 stackSlot = wrapper.querySelector('#elStackSlot');
const webSlot = wrapper.querySelector('#elWebSlot');
const qItem = wrapper.querySelector('#elQItem');
const qEmpty = wrapper.querySelector('#elQEmpty');
const loop = wrapper.querySelector('#elLoop');
const loopNote = wrapper.querySelector('#elLoopNote');
const lines = wrapper.querySelectorAll('.el-code .ln');
const c1 = wrapper.querySelector('#cLine1');
const c3 = wrapper.querySelector('#cLine3');
const c2 = wrapper.querySelector('#cLine2');
const caption = wrapper.querySelector('#elCaption');
const count = wrapper.querySelector('#elCount');
const prevBtn = wrapper.querySelector('#elPrev');
const nextBtn = wrapper.querySelector('#elNext');
const steps = [
{
line: 1, stack: 'console.log("1")', web: 'idle', queue: false, loopChecking: false,
loopNote: 'is the stack empty?', logs: [],
caption: '<strong>console.log("1")</strong> runs directly on the call stack — synchronous code always goes here first.'
},
{
line: 2, stack: 'setTimeout(...)', web: 'idle', queue: false, loopChecking: false,
loopNote: 'is the stack empty?', logs: ['1'],
caption: '<strong>setTimeout</strong> is called. It logs "1" already happened — now the timer itself is about to hand off.'
},
{
line: 2, stack: 'empty', web: 'timer (0ms)', queue: false, loopChecking: false,
loopNote: 'is the stack empty?', logs: ['1'],
caption: 'The timer is handed to the browser\'s <strong>Web APIs</strong> and popped off the stack — JS moves on immediately.'
},
{
line: 3, stack: 'console.log("3")', web: 'timer (0ms)', queue: false, loopChecking: false,
loopNote: 'is the stack empty?', logs: ['1'],
caption: '<strong>console.log("3")</strong> runs next, while the timer counts down in the background, unblocked.'
},
{
line: null, stack: 'empty', web: 'idle', queue: true, loopChecking: false,
loopNote: 'is the stack empty?', logs: ['1', '3'],
caption: 'The timer finishes. Its callback moves into the <strong>Callback Queue</strong> — it still can\'t run yet.'
},
{
line: null, stack: 'empty', web: 'idle', queue: true, loopChecking: true,
loopNote: 'yes — pull it in', logs: ['1', '3'],
caption: 'The <strong>Event Loop</strong> keeps checking. Stack\'s empty, so it moves the callback onto the stack.'
},
{
line: 2, stack: '() => console.log("2")', web: 'idle', queue: false, loopChecking: false,
loopNote: 'is the stack empty?', logs: ['1', '3', '2'],
caption: 'The callback finally runs, logging <strong>"2"</strong> — last, even though it was written second.'
},
];
let index = 0;
function render() {
const s = steps[index];
lines.forEach(l => l.classList.toggle('is-active', Number(l.dataset.line) === s.line));
stackSlot.textContent = s.stack;
stackSlot.classList.toggle('is-active', s.stack !== 'empty');
webSlot.innerHTML = s.web === 'idle' ? 'idle' : '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>' + s.web;
webSlot.classList.toggle('is-active', s.web !== 'idle');
qItem.classList.toggle('is-active', s.queue);
qEmpty.style.display = s.queue ? 'none' : 'block';
loop.classList.toggle('is-checking', s.loopChecking);
loopNote.textContent = s.loopNote;
c1.classList.toggle('is-shown', s.logs.includes('1'));
c3.classList.toggle('is-shown', s.logs.includes('3'));
c2.classList.toggle('is-shown', s.logs.includes('2'));
caption.innerHTML = s.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>📌 Summary
JavaScript runs your main code first → async tasks wait in Web APIs →
callbacks move to the queue → event loop sends them back when the stack is free.
That’s how JavaScript remains non-blocking and smooth.