Skip to main content

Command Palette

Search for a command to run...

Synchronous vs Asynchronous JavaScript

Updated
9 min read
C
Software developer passionate about building scalable web applications with React and backend technologies. I enjoy solving problems, building projects, and sharing my learning with the community.

One concept that changes how you think about code forever.


You're learning JavaScript. Everything is going great. Your functions work, your loops loop, your variables hold their values. Then you try to fetch some data from an API, and suddenly your code does something completely unexpected — it skips ahead, runs lines out of order, and returns undefined when you expected actual data.

Welcome to the moment every JavaScript developer remembers.

This isn't a bug. This is JavaScript being asynchronous — and once you truly understand what that means, you'll never be confused by it again.


First, understand synchronous code

Before we get to async, let's make sure synchronous is crystal clear.

Synchronous means one thing at a time, in order, waiting for each step to finish before moving to the next.

console.log("Step 1 — boiling water");
console.log("Step 2 — adding tea bag");
console.log("Step 3 — waiting 3 minutes");
console.log("Step 4 — tea is ready");

Output:

Step 1 — boiling water
Step 2 — adding tea bag
Step 3 — waiting 3 minutes
Step 4 — tea is ready

Predictable. In order. Each line waits for the one before it.

This is exactly how JavaScript reads your code by default — top to bottom, one line at a time. This is called being single-threaded, which means JavaScript has exactly one "worker" executing your code. It can only do one thing at a given moment.

For simple tasks — adding numbers, rendering a list, responding to a button click — this is perfectly fine. Problems start when one of those tasks takes a long time.


The problem with slow operations

Imagine this scenario. You're building a weather app. When the page loads, you want to:

  1. Show the app title

  2. Fetch today's weather from an API

  3. Show a footer message

In a fully synchronous world, step 2 might take 2–3 seconds while waiting for the server to respond. During that wait, your single-threaded JavaScript worker is completely occupied. It can't do step 3. It can't respond to button clicks. It can't animate a loading spinner. It can't do anything.

Your page freezes.

Here's what that looks like in code:

// Imagine fetchWeather() takes 3 full seconds to complete
function fetchWeather() {
  // Simulating a slow, blocking operation
  const start = Date.now();
  while (Date.now() - start < 3000) {}  // hang for 3 seconds
  return "25°C, Sunny";
}

console.log("App loaded");          // runs immediately
const weather = fetchWeather();     // BLOCKS for 3 seconds
console.log("Weather:", weather);   // only runs after 3 seconds
console.log("Footer loaded");       // also waits — for no reason

Steps 3 and 4 have nothing to do with the weather API. But they wait anyway, because the single worker is stuck on step 2. This is blocking code — and it's the problem that asynchronous JavaScript was designed to solve.


What asynchronous actually means

Asynchronous means: start the task, move on to other work, and come back to handle the result when it's ready.

Think about how a restaurant works. You walk in, order your food, and the waiter doesn't stand frozen at your table until your meal is cooked. They take your order, submit it to the kitchen, and immediately go serve other tables. When your food is ready, they bring it to you.

The waiter is asynchronous. They're not blocked by your kitchen wait time.

JavaScript works the same way for async operations. It can say: "Start fetching this data. While that's happening, I'll keep running other code. When the data arrives, I'll come back and handle it."

console.log("App loaded");          // runs immediately — Step 1

fetch('https://api.weather.com')    // kicks off the request — Step 2
  .then(response => response.json())
  .then(data => {
    console.log("Weather:", data);  // runs WHEN data arrives — Step 4
  });

console.log("Footer loaded");       // runs immediately — Step 3
// doesn't wait for the fetch at all

Output:

App loaded
Footer loaded
Weather: { temp: "25°C" }          ← arrives later, after the response comes back

Notice the order: "Footer loaded" prints before "Weather" — even though the weather code was written first. That's async. The fetch request was kicked off, JavaScript moved on without waiting, and the weather handler ran later when the data actually arrived.


The two tools for handling async: callbacks and promises

When JavaScript says "I'll come back when the data is ready" — what exactly comes back? Two mechanisms handle this.

Callbacks

A callback is a function you hand to an async operation, saying: "When you're done, call this."

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

console.log("This runs right now");

Output:

This runs right now
This runs after 2 seconds

setTimeout is async. It starts a timer, hands JavaScript the callback function, and immediately lets the next line run. When 2 seconds pass, JavaScript comes back and calls the function.

Promises

Promises are the modern, cleaner way to handle async results. A Promise represents a value that isn't available yet, but will be — or has failed trying.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    console.log("Data received:", data.title);
  })
  .catch(function(error) {
    console.log("Something went wrong:", error);
  });

console.log("This runs while fetch is in progress");

The .then() chain only runs when the data is available. The .catch() only runs if something goes wrong. Meanwhile, the rest of your code runs freely.

async/await — the cleanest syntax

Modern JavaScript lets you write async code that looks synchronous, using async and await:

async function loadWeather() {
  console.log("Fetching weather...");

  const response = await fetch('https://api.weather.com/today');
  const data = await response.json();

  console.log("Weather:", data.temperature);
}

loadWeather();
console.log("This still runs while weather loads");

The await keyword pauses only the function it's inside — the rest of your program keeps running. It's the clearest, most readable way to write async code and what most modern JavaScript codebases use today.


Everyday examples of async in JavaScript

Async isn't just for API calls. You encounter it constantly:

TimerssetTimeout and setInterval are async. They register a callback and let the rest of your code run while the clock ticks.

setTimeout(() => console.log("Timer fired!"), 1000);
console.log("I run first");  // prints before "Timer fired!"

API calls — any fetch() is async. Your UI doesn't freeze while waiting for a server response.

async function getUser(id) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

Reading files — in Node.js, reading a file from disk is async. The rest of your server can handle other requests while waiting for the file system.

const fs = require('fs').promises;

async function readConfig() {
  const content = await fs.readFile('config.json', 'utf8');
  return JSON.parse(content);
}

User interactions — event listeners are async by nature. addEventListener registers a callback and returns immediately. The callback only fires when the user actually clicks.

button.addEventListener('click', function() {
  console.log("Button clicked!");
});
// JavaScript doesn't wait here — it moves on immediately

Side by side — the mental model

Here's the simplest possible way to think about this:

Synchronous Asynchronous
Execution One task at a time Multiple tasks in progress
Waiting Blocks everything else Continues other work
Analogy One cashier, one customer Chef starts your order, serves others while cooking
Risk Freezes the app on slow tasks More complex to write and debug
Use when Quick, in-memory operations Network calls, timers, file I/O

Why this matters more than you think

This isn't just a JavaScript trivia question. Understanding sync vs async changes how you design code.

If you build a to-do list with a "Save" button, and saving means making an API call — you need to know that the save is async. While saving, the user might click something else. Your UI must stay responsive. You need a loading state. You need error handling.

If you write a Node.js server and handle each request synchronously — one blocking operation can stall every other user hitting your server at the same time. One slow database query freezes your entire app for everyone.

If you don't understand that fetch() is async, you'll write code like this:

// This doesn't work — data hasn't arrived yet when you try to use it
let data;
fetch('/api/todos').then(r => r.json()).then(d => { data = d; });
console.log(data);  // undefined — fetch hasn't finished yet!

And spend an hour wondering why data is always undefined.

Understanding async means understanding time in your programs — knowing which operations take real-world time, and writing code that handles that gracefully instead of pretending everything is instant.


The one-sentence summary

Synchronous code does one thing at a time and waits. Asynchronous code starts a slow operation, keeps running everything else, and handles the result when it eventually arrives.

That's it. Once you deeply internalize that distinction, the rest — callbacks, promises, async/await, the event loop — are just different tools for expressing the same idea.


Found this useful? Follow for more JavaScript fundamentals explained clearly. The next topic worth diving into: how the JavaScript Event Loop actually works under the hood — the engine that makes all of this async magic possible.


Tags: JavaScript · Web Development · Programming · Beginners · Node.js

More from this blog

Why Node.js is Perfect for Building Fast Web Applications

Every technology makes a bet. Node.js's bet was this: most web applications aren't slow because they do too much computation. They're slow because they spend most of their time waiting — waiting for a database to respond, waiting for a file to load, waiting for an external API to return. If you build a runtime optimised around that specific reality, you get something genuinely fast for the work most web apps actually do.

May 9, 202611 min read1
C

Chetan Chauhan | Tech Blog | chetan71

41 posts