JavaScript Threading
10 Pages
English
Edit
1. Concurrency on One Thread
2. Parallelism Beyond Main Thread
3. Browser Event Loop Walkthrough
4. Microtasks and Promise Priority
5. Timer Delay Realities
6. Rendering and Frame Budget
7. Node Event Loop Phases
8. Node I/O and Thread Pool
9. CPU Blocking Symptoms
10. Cooperative Scheduling Strategies
1. Concurrency on One Thread
JavaScript often feels multi-threaded because many operations make progress without stopping the current function, but ordinary browser JavaScript runs user code on one main thread. Concurrency means the program can manage several in-flight tasks, such as a fetch, timer, and user click, while executing only one callback at a time. Parallelism means two pieces of JavaScript actually run at the same instant, which requires workers, native code, or a host thread pool. The event loop creates the illusion of overlap by running a piece of code to completion, then choosing the next queued callback. This matters because asynchronous syntax changes when work resumes, not how many CPU cores execute the code.
Landscape
Fullscreen
2. Parallelism Beyond Main Thread
True parallelism in JavaScript usually appears when work leaves the main execution thread. In browsers, Web Workers run JavaScript in separate global contexts and communicate through messages rather than shared DOM access. In Node.js, Worker Threads can run JavaScript on additional threads, while some APIs also use a libuv thread pool for native work. Parallelism is useful for CPU-heavy tasks such as image processing, parsing large files, compression, or cryptography. The tradeoff is coordination cost: data must be copied, transferred, or shared carefully, and results arrive asynchronously. Parallelism improves responsiveness only when the overhead is smaller than the saved main-thread time.
Landscape
Fullscreen
3. Browser Event Loop Walkthrough
The browser event loop connects JavaScript execution with user input, networking, timers, microtasks, layout, paint, and compositing. A typical turn begins by taking one task from a task queue, such as a click handler, timer callback, or script execution. After that task finishes and the call stack is empty, the browser drains the microtask queue, which includes promise reactions and queued microtasks. Only then can it consider rendering updates, depending on frame timing and whether visual changes are pending. This ordering explains why a long callback delays everything after it: no other task, microtask checkpoint, or paint can interrupt JavaScript that is currently running.
Landscape
Fullscreen
4. Microtasks and Promise Priority
Promises resume through microtasks, not through ordinary timer tasks. After each task finishes, the host performs a microtask checkpoint and keeps running microtasks until the queue is empty. This is why promise callbacks usually run before a zero-delay timer scheduled in the same script. The priority is powerful for consistency: promise chains can settle related state before the browser handles another event. It is also risky because a microtask that continually queues more microtasks can starve timers, input, and rendering. Async functions follow the same rule: code after await resumes as a promise reaction, so it runs during a microtask checkpoint rather than as an independent timer.
Landscape
Fullscreen
5. Timer Delay Realities
Timers schedule future tasks, but they do not guarantee exact execution time. setTimeout places a callback into the timer task source after at least the requested delay, then the callback must still wait for the call stack to clear, microtasks to drain, and earlier tasks to finish. Nested timers may be clamped by browsers, inactive tabs may be throttled, and Node.js timers are also affected by busy JavaScript. setInterval can drift if each callback takes longer than expected, because intervals do not make the main thread preempt current work. For precise repeated scheduling, measure actual time, compensate for drift, or use requestAnimationFrame for visual updates tied to screen refresh.
Landscape
Fullscreen
6. Rendering and Frame Budget
Rendering work is coordinated with the event loop but cannot happen while JavaScript monopolizes the main thread. A visual update usually requires style recalculation, layout, paint, and compositing, and browsers try to fit this near display refresh. requestAnimationFrame schedules a callback before the next paint, making it a better place for reading animation time and applying visual changes than setTimeout. However, the callback itself still runs on the main thread, so expensive loops inside it will drop frames. Smooth animation depends on keeping per-frame JavaScript small, batching DOM reads and writes, and moving nonvisual CPU work away from the rendering-critical path.
Landscape
Fullscreen
7. Node Event Loop Phases
Node.js uses V8 for JavaScript and libuv for the event loop, with phases that organize different kinds of callbacks. Timers run callbacks whose delay has expired, pending callbacks handle some deferred system operations, poll waits for I/O and runs many I/O callbacks, check runs setImmediate callbacks, and close callbacks handle closed resources. Between these phases, Node drains special queues, including process.nextTick and promise microtasks, with nextTick receiving especially high priority. The phase model explains why setTimeout and setImmediate can appear in different orders depending on whether they are scheduled from top-level code or inside an I/O callback.
Landscape
Fullscreen
8. Node I/O and Thread Pool
Not all asynchronous Node.js work is handled the same way. Network sockets are usually readiness-based and coordinated by the operating system through the event loop, while file system calls, DNS lookup variants, compression, and crypto may use libuv’s worker thread pool. Those native worker threads can run in parallel with JavaScript, but their callbacks still return to the main JavaScript thread when complete. Increasing the thread pool size can help some workloads but can also increase contention. The key distinction is that asynchronous I/O prevents JavaScript from waiting idly, yet the callback that processes the result must still be fast enough to avoid delaying other events.
Landscape
Fullscreen
9. CPU Blocking Symptoms
CPU-heavy JavaScript blocks the main thread because the engine cannot pause a running function to process input, paint, or run another callback. In the browser, this appears as frozen scrolling, delayed clicks, late timers, stalled animations, and long tasks in performance tools. In Node.js, it appears as high event-loop lag, slow HTTP responses, delayed timeouts, and poor throughput even when asynchronous I/O is used. Common causes include large JSON parsing, complex regular expressions, synchronous compression, huge array transformations, and cryptographic loops. The fix is to reduce work, split it into chunks, stream data, cache results, use native async APIs, or move CPU work to workers.
Landscape
Fullscreen
10. Cooperative Scheduling Strategies
When work must stay on the main thread, cooperative scheduling keeps applications responsive by yielding regularly. Instead of processing a million records in one uninterrupted loop, process a slice, schedule the next slice, and let input, rendering, and microtasks run between slices. setTimeout can yield to later tasks, requestAnimationFrame is useful for visual batches, requestIdleCallback can run low-priority browser work when time is available, and scheduler APIs may provide priority-aware yielding where supported. Promises alone are not a good yield for rendering because microtasks drain before paint. For truly heavy CPU work, cooperative slicing helps, but workers are usually the more scalable solution.
Landscape
Fullscreen

Knowledge.
Made Visual.
Create your first visual book and get started