From JavaScript to Node.js: Understanding What Really Happens Behind the Scenes (Part 3)
Synchronous vs Asynchronous Node.js: What Really Happens Inside fs.readFile()?
In the previous article, we learned that Node.js is much more than the V8 engine.
We discovered that a simple file operation travels through multiple layers:
JavaScript
↓
V8 Engine
↓
Node APIs
↓
C++ Bindings
↓
libuv
↓
Operating System
↓
Hardware
But one important question still remains unanswered.
If reading a file requires communicating with the operating system and the storage device...
Why doesn't Node.js freeze while waiting for the file?
To answer that, we first need to understand one of the most important ideas in computer science.
What Does "Blocking" Actually Mean?
Most tutorials define blocking like this:
"Blocking means the program waits."
That definition is correct...
but it doesn't explain what is actually waiting.
Let's look deeper.
Consider this program.
console.log("Start");
const fs = require("fs");
const data = fs.readFileSync("message.txt", "utf8");
console.log(data);
console.log("End");
Many developers say:
"Node waits."
But technically, that's not what happens.
The current JavaScript execution cannot continue because the current function has not finished.
The JavaScript thread is occupied.
Imagine the execution like a single employee working at a desk.
Current Task
↓
Read File
↓
Not Finished Yet
Until that task finishes...
the employee cannot start another task.
This is called blocking.
Understanding the Call Stack
Before discussing asynchronous programming, we need to understand the Call Stack.
The Call Stack is a data structure that keeps track of which JavaScript function is currently executing.
Imagine it as a stack of books.
Top
console.log()
main()
Bottom
Whenever a function starts, it is pushed onto the stack.
When it finishes, it is removed.
Only the function at the top of the stack can execute.
JavaScript executes one stack frame at a time.
This is one of the reasons JavaScript is called single-threaded.
Example
function greet() {
console.log("Hello");
}
console.log("Start");
greet();
console.log("End");
Call Stack visualization
console.log("Start")
↓
Removed
↓
greet()
↓
console.log("Hello")
↓
Removed
↓
console.log("End")
Only one frame executes at a time.
There is no parallel execution happening here.
What Happens Inside readFileSync()?
Let's follow the execution carefully.
const fs = require("fs");
console.log("1");
const text = fs.readFileSync("notes.txt", "utf8");
console.log(text);
console.log("2");
Execution begins.
console.log("1")
↓
Output
1
Next comes
fs.readFileSync(...)
Node sends the request down the runtime layers.
JavaScript
↓
Node API
↓
C++ Binding
↓
Operating System
↓
SSD
At this moment...
JavaScript cannot continue.
The Call Stack still contains
readFileSync()
Until it returns,
this line
console.log("2");
cannot execute.
The result arrives.
The stack becomes free.
Execution continues.
Final Output
1
(File Content)
2
Nothing surprising happened.
The execution simply stopped until the file arrived.
This is synchronous execution.
Why Is It Called Synchronous?
Because every operation happens in sequence.
Task A
↓
Complete
↓
Task B
↓
Complete
↓
Task C
Nothing jumps ahead.
Nothing overlaps.
Every step waits for the previous one.
Now Let's Look at readFile()
const fs = require("fs");
console.log("1");
fs.readFile("notes.txt", "utf8", (err, text) => {
console.log(text);
});
console.log("2");
Many beginners imagine that JavaScript starts another thread.
It doesn't.
Let's see the real flow.
Step 1
console.log("1")
Output
1
Step 2
JavaScript reaches
fs.readFile(...)
Node prepares a native request.
The request moves through
Node API
↓
C++
↓
libuv
↓
Operating System
Notice something important.
The file has not been read yet.
Only the request has been submitted.
Step 3
At this point...
JavaScript has nothing left to do with that request.
The current function finishes immediately.
The Call Stack becomes free.
Now JavaScript executes
console.log("2");
Output becomes
1
2
The file still hasn't arrived.
Step 4
The operating system eventually finishes reading the file.
The data returns.
SSD
↓
Operating System
↓
libuv
↓
Node Runtime
The callback is now ready.
Later, when JavaScript is free to execute it, the callback runs and prints the file.
Final Output
1
2
Hello World
This is why asynchronous code appears to "continue."
JavaScript didn't keep reading the file.
It simply delegated that responsibility.
The Biggest Misconception
People often say
"Node.js performs asynchronous operations."
That sentence is incomplete.
A better statement is
Node.js delegates long-running operations to the operating system (through libuv and native bindings), allowing JavaScript to continue executing other work while the result is being prepared.
That is the real reason asynchronous programming exists.
Why Doesn't V8 Read the File Directly?
Because V8 was never designed to understand operating systems.
V8 understands JavaScript.
It understands things like
const numbers = [1,2,3];
numbers.map(x => x * 2);
It does not understand
- NTFS
- ext4
- APFS
- TCP sockets
- DNS
- File permissions
Those belong to the operating system.
Node bridges that gap.
Does libuv Read the File?
Another common misconception.
Many developers think
"libuv reads the file."
Not exactly.
In most file operations, the operating system ultimately performs the actual file read.
libuv coordinates the asynchronous workflow, abstracts platform differences, and integrates the result back into Node.js in a consistent way across operating systems.
Think of libuv as an expert traffic controller—not the storage device itself.
Why Is Node.js Fast?
Many articles say
"Node.js is fast because it is asynchronous."
That's only part of the story.
Node.js is fast because it avoids wasting the JavaScript thread waiting for slow I/O operations.
Imagine a restaurant.
One waiter.
One customer asks for coffee.
Brewing coffee takes five minutes.
A poor workflow would have the waiter stand beside the coffee machine doing nothing.
A better workflow is:
- Take the order.
- Give it to the kitchen.
- Serve other customers.
- Return when the coffee is ready.
Node.js follows the second model.
The JavaScript thread stays productive.
Synchronous vs Asynchronous
| Synchronous | Asynchronous |
|---|---|
| Blocking | Non-blocking (for the JavaScript thread) |
| Waits for completion | Delegates work and continues |
| Simpler control flow | Better scalability for I/O |
| Can freeze execution | Keeps the thread available for other work |
Neither is "better" in every situation.
Sometimes synchronous code is exactly what you need—especially during startup scripts, configuration loading, or command-line tools.
Engineering is about choosing the right tool.
Key Takeaways
After reading this article, you should be able to explain these ideas confidently:
- JavaScript executes on a single main thread.
- The Call Stack allows only one JavaScript function to execute at a time.
-
readFileSync()blocks because the current execution cannot continue until it returns. -
readFile()submits work to the Node runtime and operating system, allowing JavaScript to continue. - V8 executes JavaScript—it does not understand operating-system resources.
- Node.js extends JavaScript by providing native APIs and runtime infrastructure.
- libuv helps Node provide efficient, cross-platform asynchronous I/O.
These are not just interview concepts.
They are the foundation of every Express server, every database query, every API request, and every backend application built with Node.js.
Coming Next
In Part 4, we'll explore one of the most misunderstood topics in Node.js:
Modules,
require(),module.exports,exports, Module Wrapper Function, and Module Cache
We'll answer questions like:
- What actually happens when you write
require("fs")? - Is
require()a JavaScript feature or a Node.js feature? - Why is every file treated as a separate module?
- How does Node avoid loading the same module twice?
- What is the Module Wrapper Function?
- Why do
exportsandmodule.exportssometimes behave differently?
By the end of Part 4, you'll understand Node.js modules from the inside out—not just how to use them, but how they work.
Top comments (0)