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
// ThirdSimple 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 arrivesThis 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.
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.
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.
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.
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
}| Synchronous | Asynchronous | |
|---|---|---|
| Execution | One line at a time, in order | Starts operation, moves on, comes back later |
| Blocking | Yes — waits for each operation | No — other code runs while waiting |
| Good for | Simple calculations, logic | Network requests, timers, file reads |
| Handles slow operations | Freezes the page | Keeps 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