惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
GbyAI
GbyAI
T
Threatpost
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
The Hacker News
The Hacker News
Y
Y Combinator Blog
Blog — PlanetScale
Blog — PlanetScale
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
I
Intezer
美团技术团队
S
Schneier on Security
I
InfoQ
Project Zero
Project Zero
S
SegmentFault 最新的问题
IT之家
IT之家
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
CERT Recently Published Vulnerability Notes
博客园 - 司徒正美
Security Latest
Security Latest
G
Google Developers Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Cisco Talos Blog
Cisco Talos Blog
L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
L
Lohrmann on Cybersecurity
G
GRAHAM CLULEY
Engineering at Meta
Engineering at Meta
L
LangChain Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cisco Blogs
大猫的无限游戏
大猫的无限游戏
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
雷峰网
雷峰网
V
V2EX
The Register - Security
The Register - Security
A
Arctic Wolf
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Tor Project blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Security Blog
Microsoft Security Blog
Stack Overflow Blog
Stack Overflow Blog
Vercel News
Vercel News
Spread Privacy
Spread Privacy
H
Help Net Security
H
Heimdal Security Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Demystifying the Node.js Event Loop: From Hardware Realities to Runtime Truth
Manoj Khatri · 2026-06-15 · via DEV Community

When developers first try to learn the Node.js Event Loop, they are usually handed a circular diagram of six phases and a wall of text filled with technical jargon. If you try to memorize those phases on day one, it feels like blind cramming. A week later, the entire concept completely slips your mind.

To truly master the Event Loop, we need to stop treating it like a magical black box. We need to look at how computer hardware works, understand why Node.js was created from first principles, and figure out why every single phase exists based on practical mechanical necessity, no rote learning required.


1. The Core Problem: Hardware Speed Mismatches

To understand why the Event Loop exists, we must look at a fundamental reality of computer architecture: The massive speed gap between your CPU and input/output (I/O) devices.

Your computer’s CPU is blindingly fast, executing billions of operations per second. However, reading a file from a Hard Drive or waiting for data to travel across the internet (Network I/O) is incredibly slow compared to the CPU.

In traditional synchronous programming, if your code dictates a heavy file read, the CPU physically stops and sits idle:

// Synchronous (Blocking) Code
const fs = require('fs');

console.log("Start reading file...");
const data = fs.readFileSync('massive-video.mp4'); // The CPU drops everything and waits here!
console.log("File read complete. Processing data...");

While the hard drive spins and looks for that video file, your highly expensive CPU is completely blocked. It cannot handle any other incoming user requests. The server freezes.

Ryan Dahl, the creator of Node.js, asked a brilliant question: Why make the CPU sit idle during I/O operations?


2. The Foundation: Offloading to the Operating System

Node.js solves this by utilizing a hidden superpower: Your Operating System (Windows, Linux, or macOS).

Your operating system already possesses highly optimized, multi-threaded C++ subsystems designed to handle files and networks in the background (epoll on Linux, kqueue on macOS, and IOCP on Windows).

When you write asynchronous code in Node.js, you aren't telling JavaScript to wait for the file. You are telling Node.js to hand the job over to the Operating System and immediately free up the single JavaScript main thread:

// Asynchronous (Non-Blocking) Code
const fs = require('fs');

console.log("Start reading file...");

fs.readFile('massive-video.mp4', (err, data) => {
  console.log("File read complete inside callback!");
});

console.log("Look! The main thread is free to run this line instantly!");

While the OS handles the heavy lifting of reading that file in the background, your main JavaScript thread moves forward to execute other lines of code without skipping a beat.


3. What is the Event Loop?

Now, a logical question arises: Once the OS finishes reading that file or fetching that network request in the background, how does your JavaScript code actually receive the data and run the callback function (err, data) => { ... }?

This scheduling and orchestration layer is exactly what the Event Loop is. At its absolute lowest architectural level (written in C++ inside Node's core libuv engine), the Event Loop is fundamentally just a continuous while loop.

// A simplified conceptual look at the Event Loop's inner while loop
while (is_app_still_running) {
    // Check various checkpoints sequentially...
}

This loop runs continuously. It asks the operating system, "Are any background tasks done yet? If yes, give me their callback functions so I can run them on the main thread."


4. The 6 Phases: Deconstructed by Practical Logic

Because a computer handles a clock timer, a network socket, and a local file using completely different hardware channels, the Event Loop must split its single while loop cycle (called a Tick) into distinct checkpoints or Phases.

Let's look at the absolute mechanical why behind every single phase in the exact order they execute:

Phase 1: Timers Phase (The Clock Alignment)

  • The Logic: Time is the absolute anchor of computer systems. Before checking files or networks, the loop must instantly check the system clock heap. It asks, "Did I promise any code that it would execute after a certain number of milliseconds?" If you had a setTimeout(..., 1000) and 1 second has passed, that callback gets executed first so your application's timing schedules don't drift.

Phase 2: Pending Callbacks Phase (The Emergency Clinic)

  • The Logic: We do not live in a perfect world; background operations fail. Suppose in the previous tick, your app tried to write to a TCP socket, but the recipient closed the connection, causing a brutal operating system-level error (ECONNREFUSED). Before Node grabs fresh files or new internet requests, it must handle and report these lingering background system failures first to prevent the engine from crashing. Think of this phase as an emergency clinic cleaning up the wreckage of past failed operations before opening the doors to new work.

Phase 3: Idle, Prepare Phase (The Engine Warm-Up)

  • The Logic: This phase is completely misunderstood because no user JavaScript code runs here. It exists purely for Node's internal C++ engine (libuv). Just like a motorcycle rider idling and revving their engine for a minute to warm up the oil before hitting a high-speed highway, libuv uses this brief pause to internally recalibrate its memory pointers and thread pools right before jumping into the heaviest traffic hub of the loop.

Phase 4: Poll Phase (The Main Traffic Hub & The Epoll Sleep)

  • The Logic: This is the heart of Node.js where all your active backend logic happens: incoming HTTP requests, database query responses, and incoming file data streams (fs.readFile).
  • The Power of Pausing (When does it halt vs. exit?): A common misconception is that the loop endlessly spins here consuming 100% CPU. It doesn't.
  • Scenario A (Exit): If your code has no open servers or active handles (like a simple script with just a console.log), Node sees there is no future work coming. The loop bypasses pausing entirely and cleanly exits (halts) the process.
  • Scenario B (Sleep): If you are running a live backend server (http.createServer().listen(3000)), you have left a network port open. If no users are hitting your site right now, the loop enters the Poll Phase and literally goes to sleep. Using kernel-level OS mechanisms like epoll_wait (Linux), Node tells the OS: "My CPU thread is going to sleep right here. Don't waste energy spinning. Wake me up only when a network packet hits Port 3000 or a background timer alerts me."

🚨 Deep Dive: How Does the Poll Queue Actually Fill Up?

Here is a massive myth: “When the OS finishes reading a file, it reaches inside Node.js and pushes the callback into the Poll Queue.” This is completely false. Due to process isolation and security boundaries, the Operating System Kernel cannot directly modify Node.js's internal runtime queues.

Instead, a fascinating bridge mechanism takes place:

1. Node.js (libuv)  ──► Registers I/O request with the OS Kernel
2. OS (Background)  ──► Reads the file ──► Rings a bell in the Kernel (Sets an Event Flag)
3. Event Loop       ──► Hits the Poll Phase ──► Calls epoll_wait() to check for flags
4. OS Kernel        ──► Responds: "Yes, File XYZ is ready. Here is the data."
5. Node.js (libuv)  ──► Manually instantiates the JS callback and pushes it into the Poll Queue

The OS behaves exactly like a kitchen chef. It doesn't walk out to the dining room to serve the customer's plate (the Poll Queue). Instead, the chef rings a bell when the food is ready. The waiter (the Event Loop) hears the bell, goes to the kitchen window, fetches the data, and places the callback down onto the queue line himself.


Phase 5: Check Phase (The Immediate Bypass)

  • The Logic: This phase belongs exclusively to setImmediate(). It was designed with a very specific structural sequence: it sits immediately after the Poll Phase. If you are currently running a callback inside the Poll Phase (like reading a file) and you want to say, "The moment this file block finishes, bypass all other queues, don't circle back to check the clock timers yet, just run this specific piece of code next," dropping it into setImmediate() ensures it gets executed the second you exit the Poll station.

Phase 6: Close Callbacks Phase (The Janitor Shift)

  • The Logic: In clean software architecture, you always execute your active logic (Timers, Networks, Files) before sweeping away the garbage. When a resource cleanly shuts down (like a database closing via db.close() or a WebSocket disconnecting via socket.on('close')), those wrapping cleanup callbacks are safely processed at the very end of the tick. It's the janitor cleaning up the room before the loop resets back to Phase 1.

5. Why Timers Must Come Before Poll: The Deadlock Reality

To understand why the sequence flows the way it does, let's explore exactly how long the loop sleeps during Phase 4, and the severe architectural bug that would occur if Timers were executed at the end of a cycle.

How Long Does the Poll Phase Sleep?

When the loop enters the Poll Phase and decides to sleep via epoll_wait(), it passes a specific timeout parameter to the OS Kernel to determine exactly when to wake up:

  • Infinite Sleep: If your app is a basic live server with no active timers pending, the loop tells the OS: "Put me to sleep indefinitely. Do not wake me up until a user hits my network port."
  • Calculated Sleep: If you have a background setTimeout(..., 5000) that still has 3 seconds remaining before expiring, the loop calculates this gap and tells the OS: "Put me to sleep, but wake me up in exactly 3000ms, even if no network requests arrive."

The "Last Phase" Timers Disaster

Imagine an alternate universe where the creators of Node.js placed the Timers phase at the absolute end of the loop, resulting in a sequence like this:
... ──► Poll Phase ──► Check Phase ──► Close Phase ──► Timers Phase (Last)

Here is the exact deadlock that would break your application:

  1. You write a standard setTimeout(..., 10) (a 10ms timer).
  2. The Event Loop fires up and reaches the Poll Phase. There are no pending file I/O operations at this millisecond.
  3. The loop prepares to sleep and asks the system for a timeout parameter.
  4. The Catch: Because the Timers phase sits after the Poll phase, the loop hasn't actually scanned the system clock heap yet for this cycle! It has absolutely no clue that a 10ms timer is waiting in line.
  5. Believing there is zero pending work scheduled, Node tells the kernel to put the thread into an Infinite Sleep.
  6. The Deadlock: The system clock passes 10ms, 50ms, 5 seconds... your timer has expired, but the Event Loop is stuck fast asleep inside the Poll Phase, completely blind to the passing time. It will remain frozen there forever until an external user hits the website to wake it up.

To prevent this fatal flaw, Node must evaluate and align with the system clock heap at the absolute beginning of the cycle before entering any station capable of putting the thread to sleep.


6. The VIP Lane: The Callback Boundary Rule

While macro tasks wait inside their specific structural phases, Node.js manages two incredibly high-priority queues that sit completely outside the standard 6-phase wheel:

  1. The nextTick Queue: Callbacks created via process.nextTick().
  2. The Promise Microtask Queue: Callbacks created via native JavaScript Promises (.then(), .catch(), or async/await resolutions).

Unlike regular phase callbacks, microtasks do not wait for a phase transition. They operate on a strict rule called the Callback Boundary:

Take ONE regular callback from a phase
               │
               ▼
         Run callback
               │
               ▼
[Callback Boundary] ──► Drain nextTick queue completely ──► Drain Promise queue completely
               │
               ▼
Take the NEXT regular callback

The moment any single callback finishes running and pops off the execution call stack, Node.js immediately freezes the Event Loop, reaches into its pocket, and flushes the entire nextTick and Promise queues until they are absolutely empty before touching anything else.


7. Comprehensive Code Tracing

Let's look at a code snippet that ties everything we've learned together:

const fs = require('fs');

// 1. A Timer set for immediate execution
setTimeout(() => {
  console.log("Timer Callback (Phase 1)");

  process.nextTick(() => {
    console.log("NextTick inside Timer (VIP Lane)");
  });

  Promise.resolve().then(() => {
    console.log("Promise inside Timer (VIP Lane)");
  });
}, 0);

// 2. An Asynchronous File Read (Poll Phase)
fs.readFile(__filename, () => {
  console.log("File Read Callback (Phase 4 - Poll)");

  setImmediate(() => {
    console.log("SetImmediate Callback (Phase 5 - Check)");
  });
});

console.log("Main Stack Script Execution Complete.");

The Exact Console Output:

Main Stack Script Execution Complete.
Timer Callback (Phase 1)
NextTick inside Timer (VIP Lane)
Promise inside Timer (VIP Lane)
File Read Callback (Phase 4 - Poll)
SetImmediate Callback (Phase 5 - Check)

The Trace:

  • Call Stack Execution: The synchronous code executes first, immediately printing "Main Stack Script Execution Complete." The timer and file operations are handed off to the OS subsystems.
  • Tick 1 - Phase 1 (Timers): The loop finds the expired timer and runs its callback, printing "Timer Callback (Phase 1)". It schedules a nextTick and a Promise.
  • The Boundary: The timer callback finishes. Node halts the loop and checks the microtask queues. It drains the nextTick queue, printing "NextTick inside Timer (VIP Lane)", and then flushes the Promise queue, printing "Promise inside Timer (VIP Lane)".
  • Tick 2 - Phase 4 (Poll): On a later tick, the OS completes the file read. The loop runs the callback in the Poll Phase, printing "File Read Callback (Phase 4 - Poll)". It registers a setImmediate callback.
  • Tick 2 - Phase 5 (Check): Sequentially, the loop moves directly from the Poll phase into the Check phase. It picks up the fresh setImmediate task and prints "SetImmediate Callback (Phase 5 - Check)".

8. Microtask Starvency Warning

Because microtasks have absolute priority at every callback boundary, they must empty completely before the loop can move on. This introduces a dangerous vulnerability called Starvation.

If a microtask recursively schedules another microtask, the Event Loop will stay trapped processing the VIP lane forever, never moving to the next regular phase:

function starve() {
  Promise.resolve().then(() => {
    starve(); // Constantly adds a new item to the queue at the boundary
  });
}

starve();

// This timer will NEVER fire because the loop is permanently starved!
setTimeout(() => {
  console.log("This is unreachable code.");
}, 10);

Your server will completely freeze and stop accepting incoming network traffic because the loop can never reach the Poll Phase, even though your CPU metrics might show very low activity.


Summary Cheat Sheet

  • The Loop is a While Loop: Driven by libuv to fetch completed OS task notifications.
  • Phases are Buckets: Segregated checkpoints (Timers -> Pending -> Poll -> Check -> Close) designed to match distinct operating system hardware behaviors.
  • The Callback Boundary Rule: After any callback finishes running from a specific phase, the loop instantly pauses to fully drain process.nextTick and Promise queues before processing anything else.

Conclusion

By understanding the hardware speed gaps, the underlying OS offloading design, and the logical architecture behind phase sequence ordering, you don't need to rely on rote learning to explain the Node.js runtime environment.

The phases simply group callbacks based on how the operating system generates them, while the callback boundary guarantees that microtasks retain ultimate execution authority. Use this mental model, keep your macro callbacks lightweight, and you will build highly predictable, high-concurrency backend services.