Synchronous vs Asynchronous
Work Loads
The core difference between synchronous and asynchronous execution is
whether the program can do other work while waiting for a request or method
to complete.
Synchronous I/O
In synchronous I/O the caller sends a request and blocks, so when a
thread/process (caller) executes a synchronous operation. The caller freezes
execution and waits for the operation to complete. It cannot:
Execute the next line of code.
Respond to user input.
Handle other requests.
The operating system detects the thread is blocked (idle/waiting). To avoid
wasting CPU resources, the OS:
1. Saves the thread's state (registers, program counter, memory pointers).
2. Removes the thread from the CPU.
3. Swaps in a ready-to-run thread
This thread swap is a context switch. CPUs only run one thread per core at a
time. Blocked threads waste CPU cycles.
When the I/O finishes, the OS:
Marks the blocked thread as "ready".
Re-schedules it to run on the CPU (another context switch).
Restores its saved state, and execution resumes.
Synchronous vs Asynchronous Work Loads 1
Context switches are expensive. Heavy synchronous I/O (e.g., web servers
handling files/database calls) can waste 10-30% CPU time on switching.
Asynchronous I/O
In Asynchronous I/O
Caller sends a request → immediately continues working → processes
response later.
Goal: Avoid blocking execution while waiting for I/O (disk, network, etc.).
"How does the caller know when the response is ready?"
Two primary solutions exist:
1. Caller Checks Readiness (Non-Blocking Polling)
Caller periodically checks if the I/O operation is ready. Does not block the
caller; returns instantly ("ready" or "not ready").
Caller Requests I/O → Continues Work
↓
[Caller Periodically Asks: "Is my data ready?"]
↓
If Ready → Processes Data
Else → Continues Working
[Link] uses epoll asynchronous I/O API (also present in Linux) which
efficiently monitors 1000s of descriptors. Only returns descriptors with ready
events. A descriptor is an integer identifier by the Linux kernel which assigns
to an open I/O resource
Web servers (e.g., Nginx) handling 10,000+ concurrent connections using non-
blocking polling
Synchronous vs Asynchronous Work Loads 2
2. Receiver Notifies Caller (Completion-Based)
Receiver (OS/kernel) notifies caller when I/O completes. Caller registers a
callback or checks a completion queue. In Linux this is achieved through
io_uring asynchronous I/O API
Submission Queue (SQ): Caller posts I/O requests.
Completion Queue (CQ): OS posts results when done.
Zero syscalls: Kernel and user-space share queues via memory.
Key differences
Approach Pros Cons
Low latency for ready
Polling (e.g., epoll ) Wasted CPU cycles checking.
events.
Completion Zero syscall overhead; Complex setup; callback
(e.g., io_uring ) efficient. management.
Another solution that exists is
3. Thread Pool Delegation
The another way NodeJS achieves Asynchronous Operations when it can’t
achieve things via epoll or io_uring
Suppose you JavaScript program calls an async function (e.g., [Link] ).
[Link] spins up a worker thread (from its built-in pool, managed by libuv ).
The blocking I/O task (e.g., reading a 10GB file) is handed to this worker.
Main thread continues executing JavaScript (handles HTTP requests, timers,
etc.). Your code runs as if the I/O were "instant.”
The worker thread calls a blocking OS operation (e.g., read() ). OS blocks the
worker thread → removes it from CPU (context switch). When the OS finishes
I/O, it wakes the worker thread. Worker thread notifies the main thread (via
event loop), triggering your callback.
Synchronous vs Asynchronous Work Loads 3
[Main Thread] [Worker Thread] [OS/Kernel]
│ │ │
│─ [Link]() ──────▶ │
│ │─ Blocking read() ───────▶
│ (continues work) │ ⤷ Blocked │ (Processes I/O)
│ │◀─ I/O Complete ─────────│
│◀─ Callback Fired ────│ │
Synchronous and Asynchronous in Request-
response
Historically, Clients blocked until server responded (e.g., early
XMLHttpRequest). This lead to Unresponsive UIs, wasted resources.
All mainstream libraries ( fetch , Axios, HTTP clients) use async. Network latency
is unpredictable; clients must stay productive.
At client asynchronous works by sending Request & continuing execute rest of
program:
// Async request with fetch
fetch('/api/data')
.then(response => [Link]())
.then(data => [Link](data));
[Link]("Request sent, doing other work..."); // Runs IMMEDIATELY
A callback/promise executes when the response arrives. Client never idles—
processes UI events, local tasks, or parallel requests.
Key Benefits of Async Clients
Synchronous vs Asynchronous Work Loads 4
1. Responsiveness:
UIs stay interactive during network calls (e.g., browser apps).
2. Efficiency:
Single client handles multiple parallel requests.
3. Scalability:
Servers manage 10K+ clients without dedicated threads per connection.
Async clients decouple waiting from working—transforming network calls from
roadblocks into background tasks
Synchronous vs Asynchronous Work Loads 5