Skip to main content

Command Palette

Search for a command to run...

Synchronous vs Asynchronous JavaScript

Updated
3 min readView as Markdown

Picture this: you're at a restaurant. The synchronous approach is like ordering, then standing at the counter waiting for your food before taking the next order. The asynchronous approach is more like handing your order to the kitchen, then taking the next table's order while your food cooks. Same work, very different flow.

That's basically the difference in JavaScript.

What Synchronous Code Does

In synchronous code, each line waits for the previous one to finish. Execute line 1. Done? Execute line 2. Done? Line 3. Everything happens in order, one step at a time.

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

Output: First, Second, Third. No surprises.

The problem? If any of those operations takes time—reading a file, calling an API, waiting for a timer—everything after it sits blocked. Your entire app freezes until that one thing completes.

const data = fetchLargeData(); // Takes 5 seconds
console.log('Done'); // Can't run until fetch finishes

This is blocking code. The thread is stuck waiting, and nothing else can happen.

Why JavaScript Needs Asynchronous Code

JavaScript runs on a single thread. That's by design—it keeps things simple. But the web doesn't stop. Users click buttons, APIs respond, animations play, timers tick. If you had to wait for each thing to finish before moving on, your app would freeze constantly.

So JavaScript handles slow operations asynchronously. It starts a task, registers a callback, and moves on. When the task finishes later, the callback runs.

console.log('Start');

setTimeout(() => {
  console.log('Finished after 2 seconds');
}, 2000);

console.log('End');

What gets printed? Start, End, then "Finished after 2 seconds". The timer started and JavaScript immediately moved to the next line. It didn't wait.

Real-World Async Examples

Fetching data from an API is the classic case:

console.log('Fetching...');
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data));
console.log('Request sent');

The fetch starts, JavaScript says "let me know when it's done," and continues. The request fires in the background while your code keeps running.

Event listeners are asynchronous too:

button.addEventListener('click', () => {
  console.log('Clicked!');
});

JavaScript doesn't know when the user will click. It just registers the handler and moves on. The callback fires whenever it happens—nowhere near the time the code was written.

The Blocking Problem

Here's what goes wrong:

function processLargeFile() {
  const data = readFileSync('huge.csv'); // Blocks everything
  return parseData(data);
}

If you call this, your entire UI freezes until the file loads. The button clicks won't work. The loading spinner won't spin. Nothing.

The fix is asynchronous versions:

function processLargeFile() {
  readFileAsync('huge.csv')
    .then(data => parseData(data));
}

Now the file loads in the background, your app stays responsive, and your callback runs when it's done.


Understanding sync vs async isn't just academic—it shapes how you write every real JavaScript app. Synchronous code is simple but blocks the thread. Asynchronous code keeps your app alive but requires thinking in callbacks, Promises, or async/await. The key is knowing when each approach makes sense.