
Whenever there's an asynchronous task, V8 offloads it to libuv. For example, when reading a file, libuv uses one of the threads in its thread pool. The file system (fs) call is assigned to a thread in the pool, and that thread makes a request to the OS. While the file is being read, the thread in the pool is fully occupied and cannot perform any other tasks. Once the file reading is complete, the engaged thread is freed up and becomes available for other operations. For instance, if you're performing a cryptographic operation like hashing, it will be assigned to another thread. There are certain functions for which libuv uses the thread pool.
In Node.js, the default size of the thread pool is 4 threads:
UV_THREADPOOL_SIZE=4
Now, suppose you make 5 simultaneous file reading calls. What happens is that 4 file calls will occupy 4 threads, and the 5th one will wait until one of the threads is free.

Whenever you perform tasks like file system (fs) operations, DNS lookups (Domain Name System), or cryptographic methods, libuv uses the thread pool.
Now that you have enough knowledge, answer this question: Is Node.js single-threaded or multi-threaded?
If you're dealing with synchronous code, Node.js is single-threaded. But if you're dealing with asynchronous tasks, it utilizes libuv's thread pool, making it multi-threaded.
the order of execution is not gauranteed over here which thread executes first will win
Q: Can you change the size of the thread pool?
A: Yes, you can change the size of the thread pool by setting the UV_THREADPOOL_SIZE environment variable. For example, you can set it to 8 like this:
process.env.UV_THREADPOOL_SIZE = 8;
If your production system involves heavy file handling or other tasks that benefit from additional threads, you can adjust the thread pool size accordingly to better suit your needs.