Skip to main content

Command Palette

Search for a command to run...

The Foundation of Asynchronous Programming

Blocking and Non-Blocking Code

Updated
8 min read
The Foundation of Asynchronous Programming

Hey there, Welcome Back!

You must be worrying about synchronous and asynchronous programming, but do you know that blocking and non-blocking code forms the foundation of synchronous and asynchronous programming? They are the actual concepts on which these things are built upon.

It's like saying "Driving" is the action (Asynchronous), but the "Engine" (Non-blocking) is what actually allows the wheels to turn without stalling.

Well, that's why I am here to help you figure it out!

Analogy

Let's look at two different cases of purchasing an item:

Case 1

Imagine you are visiting a local store and there is a line before you. You need to wait for your turn to get to the front of the line and get what you want. It doesn't matter if you want one thing or multiple, you need to wait. Thus, this results in a waste of your time. And if it is a weekend or a holiday, you need to bring food too.

Case 2

Imagine you walk into a supermarket where there is no line, you just go to the aisle where the product you want is kept. You pick it up and move to the billing counter, pay, and leave. This all happens so fast, you don't have to worry about the line or wasting time.

By looking at the above two cases, what do you think? The local store is only good until there is no crowd. Once the crowd arrives, it is a waste of time, while the supermarket can handle the crowd effortlessly and it doesn't allow the time to go to waste.

The Nature of JavaScript

Before we move any forward, we will need to know one particular nature of JavaScript, which is that JavaScript works on a single-threaded engine. This means that the engine which runs JavaScript can only run one thing at a time.

By single-thread, it means we have only one CPU. This is exactly like the local shopkeeper where there is only one owner working, and to run some other task, we have to wait for the previous task to get completed. Now, let's get back to it.

The Programming Parallel to Shopping

In programming, we primarily have three different types of tasks:

I/O-Bound Tasks (The "Waiting" Tasks)

Reading/writing a file, receiving data from a server, database connections, API calls, timers. We don't call these "computation heavy" because the CPU (the brain of the computer) isn't actually doing any hard math or thinking. Instead, the CPU is just waiting for the Hard Drive (disk I/O) or the Internet (network I/O) to do its job. The Analogy: This is like asking a store employee to check the back room for a product. You aren't working hard; you are just waiting for them to return.

// Example of an I/O-Bound Task (Waiting for a timer)
setTimeout(() => {
    console.log("The employee brought the product from the back room!");
}, 2000);

CPU-Bound Tasks (The "Thinking" Tasks)

Loops, mathematical operations (complex calculations). These are the CPU-heavy tasks. This is when the CPU is sweating. Things like processing large images, encrypting passwords, or running a loop a billion times. The Analogy: This is like trying to calculate a 15% discount on 43 different items in your head. You have to stop walking your cart to focus your brain.

// Example of a CPU-Bound Task (Heavy Math)
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
    sum += i; // The CPU is sweating here!
}
console.log("Finally finished the calculation:", sum);

Fast/Light Synchronous Tasks (The "Routine" Tasks)

Simple conditional statements (if-else), console.log, basic math (like 1 + 1). These are executed by the CPU almost instantly. They are so fast that we don't even worry about them blocking the server. The Analogy: Walking down the aisle or looking at a price tag. It takes almost zero effort.

// Example of a Light Task
const price = 10;
if (price < 20) {
    console.log("I'll buy it!");
}

Tying it all together: "A program is never of one type"

A real-world application weaves all three of these together constantly. Imagine a user logging into a website:

  • Light Task: Check if the email format is valid (if/else).

  • I/O-Bound Task: Ask the database if the user exists (Network/DB Call).

  • CPU-Bound Task: Hash the password they entered using complex cryptography to see if it matches the database (Math/Loop).

Since real-world programs are a mix of all these tasks, and Node.js only has one main "thread" (one shopkeeper), how do you think Node.js handles a massive CPU-bound task without freezing the whole application? This is where the concept of blocking and non-blocking code comes into the picture.

Blocking v/s Non-Blocking Code

Blocking Code

Imagine we need to fetch a product price from the database, and after fetching the price, we need to apply a discount to it based on the coupons. Now, until we fetch the price of the product, we cannot proceed any further. All the operations written after that, they have to wait.

This is an example of blocking code which blocks the whole execution of the program until one task is completed.

const fs = require('fs');

console.log("1. Customer arrives at the counter.");

// This operation blocks everything else! The program FREEZES here.
const productPrice = fs.readFileSync('database.txt', 'utf8'); 

console.log(`2. Found the price: $${productPrice}`);
console.log("3. Applied discount and finished billing.");
// If another customer arrives while step 2 is happening, they have to wait.

Non-Blocking Code

Let's take the same scenario.

This time our server takes time and is smart. The server tells the engine that it is going to take a minute to fetch the product price, and it will notify you once it has fetched the price.

While the database is busy searching (an I/O-Bound Task), our program immediately moves on to the next lines of code. It starts doing other things—maybe it starts loading the product image, preparing the page layout, or even handling a completely different customer's request.

Once the database finally finds the price and sends it back, our program gets a "tap on the shoulder." It then takes that price, applies the coupon discount, and finishes the task. This is non-blocking code. It initiates a time-consuming task but refuses to halt the entire program while waiting for it to finish.

const fs = require('fs');

console.log("1. Customer asks for a product.");

// We ask the file system to read the file, but we DON'T wait. 
fs.readFile('database.txt', 'utf8', (error, productPrice) => {
    // This callback function gets a "tap on the shoulder" later when the file is ready.
    console.log(`3. Found the price: $${productPrice}. Finished billing Customer 1.`);
});

// The code keeps moving instantly!
console.log("2. Helping the next customer while the database searches...");

Why does this matter to us?

Okay. We learned about the different types of programs, what blocking code is, and what non-blocking code is. But why does this affect us? This matters because of Concurrency and Saving Resources—the ability to deal with many things at once while saving resources, especially money.

Concurrency

Imagine your website goes viral and 1,000 users log in at the exact same second.

  • If you use Blocking Code: User 1 asks for their profile. The server stops, waits for the database, and completely ignores the other 999 people. By the time it gets to User 100, that person has been staring at a frozen screen for 5 minutes and has already closed the tab.

  • If you use Non-Blocking Code: The server takes User 1's request, hands it to the database, and instantly pivots to take User 2's request, then User 3's, and so on. It can handle all 1,000 requests in a fraction of a second, and then just process the database answers as they pop back up via the Event Loop.

Resources (Money!)

Imagine you pay $100 for running the server for 1 hour.

  • If you use Blocking Code: User 1 asks for their profile. The server stops, waits for the database, and completely ignores the other 999 people. By the time it gets to User 100, that person has been staring at a frozen screen for 5 minutes and has already closed the tab. And by the time we reach our 1000th person, one hour has passed and you are billed $100 for just fetching the records.

  • If you use Non-Blocking Code: The server takes User 1's request, hands it to the database, and instantly pivots to take User 2's request, then User 3's, and so on. It can handle all 1,000 requests in a fraction of a second, and then just process the database answers as they pop back up. And once you are done with everything, only 15 minutes have passed and you have got 45 minutes of the server while paying the same amount.

This is why it matters so much to us. At the end of the day, practical programming is all about performance and cost. To fully master this efficiency, we built the concepts of Synchronous and Asynchronous Programming, which we are going to dive deep into in the next blog.

Stay tuned!

#JavaScript #WebDevelopment #NodeJS #BackendDev #LearnToCode #AsyncProgramming #DevWithTushar #Blocking #Non-blocking #Async #programming #chaicode

More from this blog

Web Devlopment Journey

48 posts

In this publication, you will find articles related to web devlopment whether it be git or node.js. There will be blogs on every single topic and more will be coming soon...