DEV Community

Cover image for Synchronous vs Asynchronous JavaScript
Pratham
Pratham

Posted on

Synchronous vs Asynchronous JavaScript

Why JavaScript doesn't just wait around — and how that changes everything about how you write code.


Let me tell you about the moment asynchronous JavaScript broke my brain.

I wrote this code, fully expecting it to print in order:

console.log("First");
setTimeout(() => console.log("Second"), 0);
console.log("Third");
Enter fullscreen mode Exit fullscreen mode

I expected: First → Second → Third.

I got: First → Third → Second.

Zero milliseconds of delay, and "Second" still printed last. I stared at my screen thinking JavaScript was broken. It wasn't. I just didn't understand how JavaScript handles time.

This is the story of synchronous vs asynchronous code, and once you get it, a massive amount of JavaScript behavior suddenly makes sense. Let me walk you through it the way it finally clicked for me in the ChaiCode Web Dev Cohort 2026.


What Does Synchronous Code Mean?

Synchronous means "one thing at a time, in order." Each line waits for the previous line to finish before it starts.

Think of it like a single-lane road. Only one car can pass at a time. If the car in front stops, everyone behind it waits.

console.log("Step 1: Wake up");
console.log("Step 2: Brush teeth");
console.log("Step 3: Make coffee");
console.log("Step 4: Start coding");
Enter fullscreen mode Exit fullscreen mode

Output:

Step 1: Wake up
Step 2: Brush teeth
Step 3: Make coffee
Step 4: Start coding
Enter fullscreen mode Exit fullscreen mode

Each line runs, finishes, and then the next one starts. Predictable. Linear. No surprises.

Synchronous Execution Timeline

Time →
─────────────────────────────────────────────────

  Step 1       Step 2       Step 3       Step 4
  ┌─────┐     ┌─────┐     ┌─────┐     ┌─────┐
  │Wake │ ──→ │Brush│ ──→ │Make │ ──→ │Code │
  │ up  │     │teeth│     │coffee│    │     │
  └─────┘     └─────┘     └─────┘     └─────┘

Each task COMPLETES before the next one STARTS.
Nothing runs in parallel.
Enter fullscreen mode Exit fullscreen mode

This is how JavaScript works by default. It's a single-threaded language — it has one call stack, one thread, and it processes one operation at a time.


The Problem: Blocking Code

Synchronous execution works perfectly until you hit a task that takes time. Not a millisecond — real time. Seconds. Maybe longer.

Real-World Example: Waiting for Data

Imagine you're building a web app. You need to:

  1. Show a loading message
  2. Fetch user data from an API (takes 2–3 seconds)
  3. Display the user data
  4. Show other page content

If everything is synchronous:

Time →
─────────────────────────────────────────────────

  Show        Fetch data       Display      Show
  loading     (waiting...)     user data    content
  ┌─────┐    ┌────────────────┐ ┌─────┐    ┌─────┐
  │     │ ──→│  2-3 SECONDS   │→│     │ ──→│     │
  │     │    │  EVERYTHING    │ │     │    │     │
  │     │    │  IS FROZEN     │ │     │    │     │
  └─────┘    └────────────────┘ └─────┘    └─────┘
                     ↑
              BLOCKING! 🚫
              Nothing else can run while waiting.
Enter fullscreen mode Exit fullscreen mode

The entire page freezes. The user can't scroll, can't click, can't do anything. The UI is unresponsive for 2–3 seconds. That's a terrible user experience.

This is called blocking — one slow task blocks everything else from running.

Code Example of Blocking

console.log("Page is loading...");

// Simulating a blocking operation (DON'T do this in real code)
const start = Date.now();
while (Date.now() - start < 3000) {
  // Busy-wait for 3 seconds — blocks EVERYTHING
}

console.log("Data loaded!");
console.log("Showing rest of the page");
Enter fullscreen mode Exit fullscreen mode

During those 3 seconds, JavaScript can't do anything else. No animations, no button clicks, no rendering. The browser might even show the "page unresponsive" dialog.


What Does Asynchronous Code Mean?

Asynchronous means "start a task, move on, and come back to it when it's done." You don't wait — you continue doing other things.

Think of it like ordering food at a restaurant. You place your order and then continue your conversation while the kitchen prepares the food. When the food is ready, the waiter brings it to you. You didn't freeze in silence staring at the kitchen door for 20 minutes.

SYNCHRONOUS (blocking):
  You order → you STARE at the kitchen → food arrives → you eat → you talk

ASYNCHRONOUS (non-blocking):
  You order → you TALK while kitchen cooks → food arrives → you eat
  (you didn't waste time waiting)
Enter fullscreen mode Exit fullscreen mode

In JavaScript, asynchronous code lets you say: "Start this task. I'll keep running other code. Call me when you're done."


Why JavaScript Needs Asynchronous Behavior

JavaScript runs in the browser. Browsers need to do a lot at the same time:

  • Respond to user clicks
  • Render animations smoothly (60 fps)
  • Fetch data from APIs
  • Run your JavaScript code
  • Update the DOM

If JavaScript blocked on every slow operation, the browser would freeze constantly. That's unacceptable for a web environment. So JavaScript uses an asynchronous model to handle time-consuming tasks without freezing the main thread.

The slow tasks get offloaded. While they're being processed elsewhere (by the browser, the network, the filesystem), JavaScript keeps running the rest of your code. When the slow task finishes, a callback is queued up and JavaScript processes it when it's free.


Asynchronous Examples: Timers and API Calls

setTimeout — The Simplest Async Example

console.log("Start");

setTimeout(() => {
  console.log("This runs after 2 seconds");
}, 2000);

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Output:

Start
End
This runs after 2 seconds
Enter fullscreen mode Exit fullscreen mode

Wait — "End" printed before the timeout message, even though the timeout line comes first in the code! Here's why:

  1. console.log("Start") runs immediately.
  2. setTimeout registers the callback and says "run this after 2 seconds." JavaScript doesn't wait — it moves on.
  3. console.log("End") runs immediately.
  4. After 2 seconds, the callback fires: console.log("This runs after 2 seconds").

The Zero-Delay Mystery (Explained!)

Remember my opening example?

console.log("First");
setTimeout(() => console.log("Second"), 0);
console.log("Third");
Enter fullscreen mode Exit fullscreen mode

Output: First → Third → Second.

Even with 0 milliseconds delay, the callback doesn't run immediately. It gets placed in the task queue and only executes after all synchronous code has finished. JavaScript always finishes what's on its plate before checking the queue.

setInterval — Repeat at Intervals

let count = 0;

const timer = setInterval(() => {
  count++;
  console.log(`Tick ${count}`);

  if (count === 3) {
    clearInterval(timer);
    console.log("Timer stopped.");
  }
}, 1000);

console.log("Timer started — code continues running...");
Enter fullscreen mode Exit fullscreen mode

Output:

Timer started — code continues running...
Tick 1       (after 1 second)
Tick 2       (after 2 seconds)
Tick 3       (after 3 seconds)
Timer stopped.
Enter fullscreen mode Exit fullscreen mode

The code after setInterval runs immediately. The ticks happen in the background.

API Calls with fetch (Preview)

console.log("Fetching user data...");

fetch("https://jsonplaceholder.typicode.com/users/1")
  .then((response) => response.json())
  .then((user) => {
    console.log(`User: ${user.name}`);
  });

console.log("This runs while fetch is working...");
Enter fullscreen mode Exit fullscreen mode

Output:

Fetching user data...
This runs while fetch is working...
User: Leanne Graham       (arrives later)
Enter fullscreen mode Exit fullscreen mode

fetch doesn't block. It fires the request, and JavaScript keeps going. When the response comes back, the .then() callback handles it.


The Asynchronous Task Queue Concept

Here's the mental model that ties everything together:

┌──────────────────────────────────────────────────────┐
│                   Call Stack                          │
│           (synchronous code runs here)               │
│                                                      │
│   console.log("Start")  ← runs immediately           │
│   setTimeout(cb, 2000)  ← registers callback, moves on│
│   console.log("End")    ← runs immediately           │
│                                                      │
└──────────────────────────┬───────────────────────────┘
                           │
              "Is the call stack empty?"
                           │
                    YES ↓  NO → keep running
                           │
┌──────────────────────────┴───────────────────────────┐
│                   Task Queue                          │
│        (async callbacks wait here)                   │
│                                                      │
│   [callback from setTimeout]  ← waiting...           │
│                                                      │
│   When call stack is empty, this moves up ↑           │
└──────────────────────────────────────────────────────┘

The EVENT LOOP continuously checks:
  "Is the call stack empty? If yes, take the next task
   from the queue and put it on the stack."
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Trace

Code: console.log("A");
      setTimeout(() => console.log("B"), 0);
      console.log("C");

Step 1:  Call Stack: [console.log("A")]      prints "A"
Step 2:  Call Stack: [setTimeout(cb, 0)]     registers cb, removed
         Task Queue: [cb]
Step 3:  Call Stack: [console.log("C")]      prints "C"
Step 4:  Call Stack: EMPTY
         Event Loop: "Stack empty? Yes → move cb from queue to stack"
Step 5:  Call Stack: [console.log("B")]      prints "B"

Output: A, C, B
Enter fullscreen mode Exit fullscreen mode

This is why "B" prints last even with a 0ms delay. The callback always waits for all synchronous code to finish before it can run.


Synchronous vs Asynchronous — Side by Side

Feature Synchronous Asynchronous
Execution One at a time, in order Start task, move on, handle result later
Blocking? ✅ Yes — waits for each task ❌ No — moves on immediately
UI impact Can freeze the page Keeps the page responsive
Use case Simple calculations, variable assignment API calls, timers, file reads, user events
Code complexity Simple, linear Requires callbacks, promises, or async/await
Example let x = 1 + 2; fetch(url).then(...)

Everyday Analogy

SYNCHRONOUS — doing laundry the blocking way:
  1. Put clothes in washer → WAIT 45 min → done
  2. Move to dryer → WAIT 60 min → done
  3. Fold clothes → done
  Total: You did NOTHING else for 2 hours.

ASYNCHRONOUS — doing laundry the smart way:
  1. Put clothes in washer → go cook dinner
  2. Washer beeps → move to dryer → go read a book
  3. Dryer beeps → fold clothes
  Total: You cooked dinner AND read a book while waiting.
Enter fullscreen mode Exit fullscreen mode

Problems That Occur with Blocking Code

1. Frozen UI

If JavaScript is busy with a synchronous task, the browser can't update the screen, respond to clicks, or animate anything. Users see a frozen, unresponsive page.

2. Poor User Experience

Nobody wants to wait 3 seconds staring at a blank screen while data loads. Async code lets you show loading spinners, skeleton screens, or partial content while data arrives.

3. Timeouts and Failures

Synchronous network requests (which are deprecated for good reason) would freeze the browser until the server responds — or until the request times out, leaving the user stuck.

4. Wasted Resources

A single-threaded language that blocks means the CPU is idle during I/O waits. Async code lets the CPU do useful work while waiting for slow operations to complete.


Let's Practice: Hands-On Assignment

Part 1: Predict the Output

console.log("1");
setTimeout(() => console.log("2"), 1000);
console.log("3");
setTimeout(() => console.log("4"), 0);
console.log("5");
Enter fullscreen mode Exit fullscreen mode

Answer: 1, 3, 5, 4, 2

  • 1, 3, 5 are synchronous — they run immediately in order.
  • 4 has 0ms delay but still waits for synchronous code to finish.
  • 2 has 1000ms delay — runs last.

Part 2: Simulate Async Data Loading

console.log("📡 Fetching user data...");

setTimeout(() => {
  const user = { name: "Pratham", role: "developer" };
  console.log(`✅ User loaded: ${user.name} (${user.role})`);
}, 2000);

console.log("⏳ Meanwhile, showing the rest of the page...");
console.log("📰 News section loaded.");
console.log("📊 Dashboard widgets loaded.");
Enter fullscreen mode Exit fullscreen mode

Output:

📡 Fetching user data...
⏳ Meanwhile, showing the rest of the page...
📰 News section loaded.
📊 Dashboard widgets loaded.
✅ User loaded: Pratham (developer)   ← arrives 2 seconds later
Enter fullscreen mode Exit fullscreen mode

Part 3: Experience Blocking vs Non-Blocking

// ❌ Blocking version — freezes everything for 3 seconds
console.log("Start (blocking)");
const start = Date.now();
while (Date.now() - start < 3000) {} // busy wait
console.log("End (blocking)"); // prints after 3 seconds of freezing

// ✅ Non-blocking version — doesn't freeze anything
console.log("Start (non-blocking)");
setTimeout(() => {
  console.log("End (non-blocking)"); // prints after 3 seconds, but nothing froze
}, 3000);
console.log("Still running other code!"); // prints immediately
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Synchronous code runs one line at a time, in order. Each line waits for the previous one to finish. Simple but potentially blocking.
  2. Asynchronous code starts a task and moves on. The result is handled later via callbacks, promises, or async/await. Non-blocking.
  3. JavaScript is single-threaded but uses an event loop and task queue to handle async operations without blocking the main thread.
  4. Blocking code freezes the UI, wastes CPU time, and creates terrible user experiences. Async code keeps everything responsive.
  5. setTimeout, fetch, event listeners, and file I/O are all asynchronous — they register callbacks that execute after all synchronous code finishes.

Wrapping Up

Understanding the difference between synchronous and asynchronous JavaScript is one of the most important mental shifts you'll make as a developer. Once you see that JavaScript doesn't "wait" for slow tasks — it moves on and comes back later — a huge amount of confusing behavior starts making sense. The order of console.log outputs, how fetch works, why your data is undefined before the API responds — it all clicks.

I'm working through all of this in the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg. This article is the foundation — next comes promises, async/await, and the full event loop picture. But understanding sync vs async first makes everything that follows much easier.

Connect with me on LinkedIn or visit PrathamDEV.in. More articles coming as the journey continues.

Happy coding! 🚀


Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode

Top comments (0)