NNODE LOOP LABruntime observatoryNNEON · Articles in 90 languages
CONNECTING
v24.18.0linux/x64
Concurrency model → suitable workload
08

Node.js vs Java, Go, and Python

Learn why Node is efficient for I/O, where the Event Loop advantage ends, and which models Java, Go, and Python provide.

PROCESS IDcurrent server
UPTIMEsince startup
LOOP DELAY P95perf_hooks
UTILIZATIONevent loop
HTTP ROUNDTRIPbrowser → server
LIVE TRACE

Timeline

READY
0 ms
Events will appear hereRun the selected scenario
#TIMESOURCEEVENT
Waiting for an experiment…
CHAPTER 08
DEEP DIVE · FROM BASICS TO CODE

Understanding: Node.js vs Java, Go, and Python

You can read this chapter before running the experiment. Then return to the live trace and match each concept to a real event.

FIRST, IN PLAIN LANGUAGE

Node is less like one slow worker and more like a dispatcher that does not stand beside every waiting order. While a database, network, or disk is working, the main JavaScript thread can serve other ready events. This saves threads during I/O waiting; it does not make heavy JavaScript parallel.

TECHNICAL FOUNDATION

One Node process contains a V8 isolate with a main JavaScript Event Loop, V8 service threads, and libuv facilities. Sockets usually rely on OS readiness, some fs/crypto/DNS work uses a bounded libuv pool, and CPU-bound JavaScript can move to Worker Threads or processes. “Node is single-threaded” describes JavaScript execution in one isolate, not the entire process.

Why it mattersA senior-level comparison connects a concurrency model to I/O wait, CPU cost, per-task memory, failure isolation, and observability instead of declaring one runtime universally superior.
WHERE THE WORK RUNS
01YOUR JS CODEfunctions · callbacks
02NODE APIsfs · crypto · timers
03V8 + LIBUVheap · loop · pool
04OPERATING SYSTEMI/O · threads · memory
01 · GLOSSARY

Terms used in this experiment

Understand the words first, then the execution order.

01

Concurrency

Multiple tasks are in progress with overlapping lifetimes; their instructions need not execute at the same instant.

02

Parallelism

Instructions actually execute simultaneously on multiple cores or hardware threads.

03

I/O-bound

Work dominated by waiting for a network, disk, database, or another external resource.

04

CPU-bound

Work limited by computation such as serialization, compression, image processing, cryptography, or large loops.

05

V8 isolate

An isolated V8 environment with its own heap and JavaScript execution state. A Worker Thread creates another isolate.

06

Throughput

Successfully completed operations per unit of time; it is not the latency of one operation.

02 · MECHANICS

What happens step by step

Each step maps to an observable runtime state.

  1. 01
    Classify the work

    Separate short JavaScript, network waits, native operations, and heavy CPU code.

  2. 02
    Node registers I/O

    The main thread delegates waiting to the OS or libuv and returns to ready callbacks.

  3. 03
    Readiness returns a continuation

    A callback or Promise continuation waits for a free JavaScript stack.

  4. 04
    CPU changes the picture

    Long synchronous JavaScript occupies the isolate and delays all of its connections.

  5. 05
    Choose a parallel boundary

    Use Worker Threads for CPU/shared memory, processes for isolation, or job queues for distributed work.

  6. 06
    Verify the model

    Compare throughput, p95/p99 latency, Event Loop delay, CPU, memory, and overload behavior.

03 · CONTEXT

Where the result needs context

These details explain why similar code can sometimes produce a different trace.

01

Java is not only heavyweight thread-per-request

Java has platform threads, NIO/reactive designs, and virtual threads. A virtual thread can unmount during blocking I/O, scaling synchronous code without making CPU work faster.

02

Go is not one OS thread per goroutine

The Go scheduler multiplexes lightweight goroutines over OS threads and can run work in parallel across available cores.

03

Python depends on implementation and build

asyncio also uses an Event Loop for I/O. Default CPython serializes much Python bytecode with the GIL, while multiprocessing, native extensions, and optional free-threaded builds change the boundary.

04

Node is not efficient at everything

Many waiting sockets are a strong fit. Large synchronous serialization or computation per request can turn one isolate into the bottleneck.

05

Architecture can outweigh the language

Connection pools, backpressure, algorithms, batching, caches, process counts, and limits often dominate the result.

01
Theory

First understand which parts of Node participate in execution.

02
Simplified code

Then remove instrumentation and focus on the central mechanism.

03
Runtime code

Finally match the model to the code that produces the live trace.

04 · Simplified code

A minimal model without instrumentation

src/demos.js · educational snippetJavaScript
const waits = Array.from({ length: 24 }, (_, index) =>
  delay(25 + (index % 4) * 10)
);

// One Event Loop coordinates all pending waits:
await Promise.all(waits);

// This CPU work occupies the main JavaScript isolate:
heavyCpuWork(180);
05 · Runtime code

The complete code executed by the scenario

This is not an alternative example: these are the functions and files used by the Run button.

ACTUAL SOURCE

The source is generated from the real server function. Scenarios using a child process or Worker include every participating file.

src/demos.js
scenario72 lines
import { performance } from 'node:perf_hooks';

const sleep = (ms) =>
  new Promise((resolve) => setTimeout(resolve, ms));

function blockMainThread(durationMs) {
  const startedAt = performance.now();
  let iterations = 0;

  // Намеренная блокировка для учебного сценария. В production так делать нельзя.
  while (performance.now() - startedAt < durationMs) {
    iterations += Math.sqrt((iterations % 10_000) + 1);
  }

  return Math.round(iterations);
}

async function runtimeModelsComparison(emit) {
  const taskCount = 24;
  const startedAt = performance.now();
  emit(
    'main-js',
    'schedule',
    `Регистрируем ${taskCount} I/O-подобных ожиданий без ${taskCount} JavaScript-потоков`,
  );

  const waits = Array.from({ length: taskCount }, (_, index) =>
    sleep(25 + (index % 4) * 10).then(() => index),
  );
  emit(
    'main-js',
    'sync',
    `Все ожидания зарегистрированы за ${(
      performance.now() - startedAt
    ).toFixed(1)} мс; стек снова свободен`,
  );

  const results = await Promise.all(waits);
  emit(
    'event-loop',
    'result',
    `${results.length} continuations выполнены тем же Event Loop после готовности таймеров`,
  );

  const timerStartedAt = performance.now();
  const delayedTimer = new Promise((resolve) => {
    setTimeout(() => {
      emit(
        'event-loop',
        'warning',
        `Таймер после CPU-блокировки вошёл в стек через ${Math.round(
          performance.now() - timerStartedAt,
        )} мс`,
      );
      resolve();
    }, 20);
  });

  emit(
    'main-js',
    'warning',
    'Теперь 180 мс CPU-bound JavaScript блокируют один главный isolate',
  );
  blockMainThread(180);
  await delayedTimer;

  emit(
    'architecture',
    'result',
    'Вывод: дешёвое ожидание I/O даёт throughput, но CPU-параллелизм требует Worker, процессов или другого runtime-подхода',
  );
}

The emit(...) calls become live-trace rows. await and Promise keep the HTTP stream open until the scenario completes.

06 · DO NOT CONFUSE

Common misconceptions

The myth is on the left; the accurate model is on the right.

MYTH

Node.js is entirely single-threaded.

ACTUALLY

One thread normally executes JavaScript for one isolate, while the process also uses service threads, the libuv pool, and optional Worker Threads.

MYTH

Async code automatically uses every CPU core.

ACTUALLY

It avoids wasting a thread while waiting. CPU parallelism needs multiple isolates/processes or a parallel native API.

MYTH

Java always creates an expensive OS thread per request.

ACTUALLY

That is one model among platform threads, NIO/reactive systems, and JVM-scheduled virtual threads.

MYTH

Python threads can never run in parallel under any conditions.

ACTUALLY

The answer depends on CPython/GIL mode, free-threaded builds, native code, and the chosen multiprocessing or asyncio architecture.

MYTH

High throughput guarantees low latency.

ACTUALLY

A service can complete many requests per second and still have poor p95/p99 tail latency.

07 · SELF-CHECK

Explain it in your own words

If you can explain the answer without quoting documentation, your mental model is starting to take shape.

  1. Why are ten thousand open sockets fundamentally different from ten thousand active CPU computations?
  2. When would you choose a Worker Thread, a process, or a BullMQ Worker?
  3. Which measurements prove an advantage instead of repeating a marketing claim?