Libuv is a high-performance, multi-platform C library that serves as the 'engine room' of Node.js. While the V8 engine is responsible for executing your JavaScript code, libuv is responsible for everything else: handling the Event Loop, managing the Thread Pool, and interacting with the Operating System for I/O tasks.
The Event Loop is actually implemented inside libuv. It is a semi-infinite loop that orchestrates which code needs to run next. It manages timers (like setTimeout), I/O callbacks, and setImmediate.
Node.js offloads heavy tasks to a background pool. This is libuv's Thread Pool. By default, it has 4 threads (though you can increase this up to 1024) which handles File System APIs, DNS Lookups, Crypto, Zlib
If a developer had to write different code for every OS, Node.js would be a nightmare to use. Libuv acts as a bridge, providing a single, consistent API that works the same way on every platform.
You're debugging a CLI tool that reads 500 small JSON files sequentially with fs.readFileSync. A senior dev suggests switching to fs.promises.readFile with Promise.all. Why would that be faster, and what part of libuv makes it possible?
A teammate says 'Node is single-threaded so it can't do parallel work.' You know libuv has a thread pool. How would you explain the difference between the main event loop thread and the worker threads to them?
You add a setTimeout(fn, 0) at the top of a script and another at the bottom. Both log timestamps. The bottom one sometimes runs first. Walk me through what libuv's timer heap and event loop phases are doing here.
Your API service hits a latency spike every night at 2am when a cron job processes 10k CSV files using fast-csv and fs.createReadStream. CPU is fine but event loop lag spikes to 2s. You suspect libuv's thread pool. How would you confirm this and what are two ways to mitigate it without rewriting the parser?
You're building a feature that calls a legacy SOAP service via HTTP. The client library only offers a synchronous API. You wrap it in a Promise and run it in a Worker Thread. A colleague asks why not just use util.promisify with the thread pool. What's the tradeoff between libuv's thread pool and Worker Threads for this case?
A production service leaks file descriptors — lsof shows thousands of open sockets. The code uses http.request with keep-alive. You suspect libuv's handle cleanup. What libuv handle types could cause this, and how would you trace which JS code isn't calling destroy() or end()?
You're designing a high-throughput ingest pipeline that receives 50k msg/sec over UDP, does light transformation, and batches writes to disk. You're evaluating whether to use Node's dgram (libuv UDP) or a native addon with io_uring. What libuv limitations around packet processing and backpressure would drive that decision?
Your team maintains a native addon that uses libuv's uv_queue_work for heavy image processing. Under load, the event loop stalls because the thread pool is saturated. You can't increase UV_THREADPOOL_SIZE (container limits). Propose two architectural changes — one at the libuv integration layer, one at the system level — to keep the event loop responsive.
A Windows-only bug: child_process.spawn with detached: true leaves zombie processes after the parent exits. On Linux it works fine. You trace it to libuv's process handle implementation. What's the difference in how libuv manages process handles on Windows vs Unix, and how would you fix the addon code to reap correctly on both?
Your org is migrating a monolithic Node.js platform to a sidecar-based service mesh. The legacy codebase has dozens of native addons that directly call libuv APIs (uv_async_send, uv_timer_start). You need a strategy to run these addons in the new environment without rewriting them. What are the libuv ABI stability guarantees across Node major versions, and how would you design a compatibility layer?
You're leading a Node.js core contribution to add first-class support for io_uring on Linux alongside the existing epoll backend in libuv. The goal is zero-code-change adoption for existing apps. What are the three hardest semantic differences between epoll and io_uring that libuv's abstraction must reconcile, and how would you validate no behavioral regressions in the event loop phases?
A critical security audit finds that libuv's uv_getaddrinfo (used by dns.lookup) doesn't honor the system's DNS-over-TLS configuration on some Linux distros. You need to patch this across all supported Node versions (18.x, 20.x, 22.x) without breaking existing apps that rely on the current fallback order. How do you scope the fix, test matrix, and rollout plan given libuv is vendored in Node?