DocsHub
Async JavaScript

Sync vs Async

Learn the difference between synchronous and asynchronous JavaScript and why async programming matters.

Sync vs Async

Before writing a single line of async code, you need to understand why it exists. And to understand that, you need to understand how JavaScript normally runs code.


Synchronous JavaScript

By default, JavaScript is synchronous. This means it runs code one line at a time, in order, waiting for each line to finish before moving to the next.

console.log("First");
console.log("Second");
console.log("Third");

// First
// Second
// Third

Simple and predictable. Line 1 runs, finishes, then line 2 runs, finishes, then line 3.

This is called blocking — each line blocks the next one from running until it is done.


The Problem With Synchronous Code

Synchronous works perfectly for simple operations. But what happens when one operation takes time?

Imagine fetching data from a server. That request might take 2 seconds. If JavaScript is synchronous — it sits and waits those 2 seconds doing absolutely nothing. The entire page freezes. No clicks work. No animations run. Nothing.

// Imagine this takes 3 seconds
const data = fetchDataFromServer(); // ← everything stops here for 3 seconds

console.log(data);
console.log("This waits 3 seconds to print");
updateTheUI();   // frozen until data arrives
handleClicks();  // frozen until data arrives

This is called blocking the main thread. JavaScript runs on a single thread — one thing at a time. If that thread is busy waiting, the whole page is unresponsive.

Start Fetch data3 seconds wait Everything frozen Data arrives Continue

For a real web app this is unacceptable. Users would see a frozen, unresponsive page every time anything talks to a server.


Asynchronous JavaScript

Asynchronous code solves this. Instead of waiting, JavaScript starts an operation, moves on, and comes back when the result is ready.

console.log("First");

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

console.log("Third");

// First
// Third
// Second — runs after 2 seconds

"Third" prints before "Second" — even though "Second" appears earlier in the code. JavaScript did not wait 2 seconds. It started the timer, moved on, and came back when the timer finished.

Start Start timer2 seconds Keep runningother code Timer done Run callback

The page stays responsive. Other code keeps running. When the async operation finishes, its result is handled.


JavaScript is Single Threaded

Here is something that confuses a lot of people. JavaScript is single-threaded — it can only do one thing at a time. So how does it handle async operations without freezing?

The answer is the event loop — a system built into the browser (and Node.js) that manages async operations.

When you start an async operation — a timer, a network request, a file read — the browser handles it in the background using its own threads. JavaScript just says "let me know when you're done" and moves on.

When the operation finishes, the browser puts the callback in a queue. The event loop checks this queue and runs the callback when the main thread is free.

JavaScript Code Start async operation Browser handles itin the background Keep runningother code Operation finishes Callback addedto queue Main thread free Event loop runsthe callback

The event loop is one of the most important concepts in JavaScript. We cover it in full detail in the Advanced section. For now just understand the big picture — JavaScript hands off async work to the browser and picks up the result when it is ready.


Real Examples of Async Operations

In real JavaScript, async operations fall into a few categories:

Timers — run code after a delay or repeatedly:

// Run once after 2 seconds
setTimeout(() => {
  console.log("2 seconds later");
}, 2000);

// Run every second
setInterval(() => {
  console.log("Every second");
}, 1000);

Network requests — fetching data from an API:

fetch("https://api.example.com/users")
  .then(response => response.json())
  .then(data => console.log(data));

User events — waiting for a click or keystroke:

button.addEventListener("click", () => {
  console.log("User clicked");
});

File operations — reading files (mainly in Node.js):

fs.readFile("data.txt", (err, data) => {
  console.log(data.toString());
});

All of these share the same idea — start the operation, do not wait, handle the result later.


The Three Ways to Handle Async in JavaScript

Over time, JavaScript developed three patterns for handling async operations — each an improvement on the last.

Callbacks1995 PromisesES6 2015 Async AwaitES2017

1. Callbacks — the original way

Pass a function to be called when the operation finishes.

fetchData(url, function(data) {
  console.log(data); // runs when data is ready
});

Works — but gets messy when you have multiple async operations that depend on each other.

2. Promises — the cleaner way

An object that represents a value that will be available in the future.

fetchData(url)
  .then(data => console.log(data))
  .catch(error => console.log(error));

Cleaner syntax, better error handling. Replaced callbacks as the standard.

3. Async/Await — the modern way

Write async code that looks and reads like synchronous code.

async function loadData() {
  const data = await fetchData(url);
  console.log(data);
}

Built on top of Promises — but reads like normal top-to-bottom code. This is what modern JavaScript uses.


Synchronous vs Asynchronous — Side by Side

// Synchronous — blocks until done
function syncOperation() {
  const result = heavyComputation(); // everything waits
  console.log(result);
}

// Asynchronous — does not block
async function asyncOperation() {
  const result = await fetchFromServer(); // JavaScript moves on
  console.log(result); // runs when ready
}
SynchronousAsynchronous
ExecutionOne line at a time, in orderStarts operation, moves on, comes back later
BlockingYes — waits for each operationNo — other code runs while waiting
Good forSimple calculations, logicNetwork requests, timers, file reads
Handles slow operationsFreezes the pageKeeps the page responsive

A Practical Example — The Difference

// ❌ Imagine if fetch were synchronous — terrible UX
const user = fetchUser();         // page freezes 1 second
const posts = fetchPosts(user.id); // page freezes another second
const comments = fetchComments(); // page freezes another second
// 3 seconds of frozen page before anything shows
// ✅ Async — page stays responsive
async function loadPage() {
  const user = await fetchUser();
  const posts = await fetchPosts(user.id);
  const comments = await fetchComments();
  // Total: still 3 seconds, but page is never frozen
  // Other interactions still work while data loads
}

Same result — but one freezes the browser and one does not.


Summary

  • Synchronous code runs one line at a time and blocks until each line finishes
  • Blocking means everything stops — no clicks, no animations, no responses
  • Asynchronous code starts an operation and moves on — comes back when it is ready
  • JavaScript is single-threaded but uses the event loop to handle async work without freezing
  • The browser handles async operations in the background and puts the callback in a queue when done
  • Three async patterns in JavaScript — callbacks, promises, and async/await
  • Use async whenever you are dealing with network requests, timers, file operations, or anything that takes time

On this page