JavaScript’s event loop vs. PHP’s multi-process model
Last updated: February 5th 2025
Introduction
Imagine two restaurants competing on the same busy street. One uses a single waiter with magic notepad (JavaScript’s event loop), and the other hires 100 waiters with one hand each (PHP’s multi-process model). Their strategies reveal why JavaScript and PHP handle tasks so differently, even though both are “single-threaded.”
Let's break it down and understand how JavaScript (Node.js) and PHP handle tasks internally.
JavaScript’s Eventloop
Event Loop is just a loop that runs forever
while (true) { // do something }
what happend in Node ?
Nodejs uses OS-specific APIs (e.g., epoll on Linux, kqueue on macOS, IOCP on Windows) to monitor I/O events. When a request (e.g., HTTP, TCP) arrives, the OS notifies libuv, which then enqueues the associated callback into the event loop.For blocking operations (e.g., file I/O), libuv offloads work to a thread pool and inserts the result into the event loop once ready.
Event Loop Phases
The event loop constantly checks for tasks in this order:
- Checks Timers and excutes callbacks from setTimeout() or setInterval().
- Checks Pending Callbacks and Runs I/O-related callbacks (e.g., network errors).
- Idle/Prepare: these are Internal system tasks (ignored by developers, you just need to know that they exist).
How Async Works
- When you call fetch() or readFile(), Node.js sends the task to the OS kernel (via libuv).
- The main thread keeps running other code as usual.
- Once the OS finishes the task, its callback is added to the queue.
- The event loop picks it up in the next iteration.
Example
console.log("Start"); setTimeout(() => console.log("Timer"), 0); fetch("https://api.com").then(() => console.log("API Done")); console.log("End");
output:
Start End Timer API Done
- The setTimeout and fetch tasks are offloaded to the OS.
- The main thread runs console.log("End") before handling their callbacks.
PHP Under the Hood
(Multi-Process Model)
One Process Per Request
- Each HTTP request starts a new PHP process (or thread).
- Example: 100 users = 100 separate processes.
Blocking Execution
If a task (e.g., database query) takes time, the process stops until it finishes.
Example:
echo "Start"; $data = file_get_contents("large_file.txt"); // Blocks here echo "End";
The process waits for file_get_contents() to finish before printing “End”.
PHP-FPM (FastCGI Process Manager)
- Uses a pool of worker processes to handle requests.
- Workers are reused, but each request still runs in isolation.
- Heavy traffic? The server spawns more workers, consuming RAM/CPU.
No Shared Memory
- Variables, sessions, and data are not shared between requests.
- Each process starts fresh.
Key Differences in Action
Scenario | JavaScript (Node.js) | PHP |
---|---|---|
100 Users Request Data | 1 thread handles all via event loop. | 100 processes (RAM overload). |
Database Query | Keeps processing other tasks while waiting. | Process blocks until query ends. |
Crash | Whole server crashes if the main thread dies. | Only one process crashes. |
Why JavaScript Scales Better
- Low Overhead: One thread + OS helpers (libuv) handle thousands of tasks.
- Non-Blocking: No wasted time waiting for I/O (files, networks, databases).
Why PHP Simpler for Basic Sites
- No Async Complexity: Code runs top-to-bottom.
- Isolation: Crashes or bugs affect only one request.
Conclusion: When to Use Which
- JavaScript: Apps needing real-time updates, high concurrency (APIs, chats).
- PHP: Sites with simple logic and low traffic (blogs, CMS).
This article was written by Ahmad Adel. Ahmad is a freelance writer and also a backend developer.