Python vs Node.js loop benchmark on ARM64 Windows: Node was 5x faster
In this article
For 10 million additions, Python took 934.5 ms and Node took 185.9 ms. The way the JIT warmed up appeared directly in the table, so the loop body mattered more than the short startup difference. Even this simple operation produced a sizable gap on my everyday ARM64 Windows machine.
A 5.0x language difference is not unusual, but I wanted to measure it on my Surface Pro 11th Edition on 2026-08-02. The point was to rule out the possibility that one side was using x64 emulation. The machine had a Snapdragon X Elite X1E80100, Windows 11 Pro 10.0.26200, AC power, and the default Balanced plan, close to normal working conditions.
The PE headers showed Machine 0xAA64 for Python 3.12.10 and Node v24.13.0. A language comparison would need to be restarted if the entry executables were running at different layers, so I checked this first. Windows on ARM64 can quietly involve compatibility layers, and I wanted to avoid calling an execution-mode difference a runtime difference. Both were native ARM64; the gap cannot be explained by one side being x64 emulation.
I used the same loop shape
The workload was almost nothing but an addition loop. Python used range(10000000), and Node used for (let i = 0; i < 10000000; i++); both produced a sum. I chose this simplicity to avoid library differences and I/O waits and look at runtime behavior itself. It is not the same workload as an everyday helper command, but it was still useful as one isolated measurement.
s = 0
for i in range(10000000):
s += i
let s = 0
for (let i = 0; i < 10000000; i++) s += i
I ran seven times and separated the first result from the median of runs two through seven. Node has a JIT, so its first run should differ from the warmed-up runs. Python can also vary with caches and resident processes, so I did not decide from one run.
Node was much faster for the loop alone
The result was:
| Runtime | First run | Median of runs 2–7 |
|---|---|---|
| Python 3.12.10 | 902.9ms | 934.5ms |
| Node v24.13.0 | 235.9ms | 185.9ms |
Python's first run was 902.9 ms and the later median was 934.5 ms. The first run being faster is an uneasy pattern that made me want to inspect it again. The difference is about 3.5%, so it is safest to treat it as measurement variation; more runs would be needed for a fine-grained comparison.
Node fell from 235.9 ms on the first run to 185.9 ms afterward. It took time for the JIT to optimize the loop, then ran warm. That was the expected shape, and it appeared on ARM64 Windows too. It made the idea of sticking with a plain loop feel less attractive.
Adding startup changes the impression slightly
Combining these loop measurements with the earlier startup measurements changes the view a little. Python's 58.0 ms startup plus its 934.5 ms loop is 992.5 ms. Node's 95.9 ms startup plus its 185.9 ms loop is 281.8 ms. With a loop this large, startup barely changes the ratio.
For a short script called hundreds of times, it is different. Python starts more lightly, while Node computes faster. I use many helper commands that touch a configuration file and finish, so this test did not change my choice of Python. Moving everything to Node from this one result would be too broad; the actual branches of work differ.
For a pure 10-million-iteration loop, however, Python's plain for was at a disadvantage. Seeing the absolute 934.5 ms result makes me more cautious about processing one million or ten million records one at a time in Python. I would like to rerun this with more iterations.
I reran it after a median bug
The table is from the second measurement. In the first version, I calculated the median in PowerShell as $sorted[[int]($n / 2)].
PowerShell's [int] uses banker's rounding, so 3 / 2 becomes 2 when there are three measurements. Index 2 in a three-element array is the maximum, so I was reporting the worst value rather than the median.
The ratio did not change dramatically, which delayed discovery. A table with Python just over 1,000 ms and Node near 250 ms still looked plausible. I changed it to [math]::Floor() and increased the measurement count from three to seven. Without that mistake I would have written the wrong numbers with more confidence; the few minutes before noticing it were uncomfortable.
There is more reason to use NumPy
This 5x ratio is probably not specific to ARM64. The same code on an x64 machine would likely point in the same direction. The useful local fact is that 10 million additions on one Snapdragon X Elite core took about 934.5 ms in a plain Python loop.
For numeric work in Python, I prefer NumPy. The installed version was 2.4.1, and replacing the loop with np.arange(10_000_000).sum() finished in a median 1.8 ms. That is 519 times faster than the 934.5 ms plain loop. At that point the better question is how to express the operation, not whether to change languages.
Not everything can be converted to NumPy. Work that touches Python objects one by one, or has substantial I/O and branching, may not fit an array operation. Still, before writing a loop for something as simple as addition, I now check whether it can be vectorized.
A measurement from a normal working machine
There are two cautions for anyone repeating this. Seven runs may not be enough. Python's first run was 902.9 ms while its later median was 934.5 ms, in the opposite order. This procedure is insufficient for comparisons whose difference is only a few percent.
I also did not remove other processes. During the measurement, msedge remained resident using 4840.3MB and Copilot used 1830.2MB. The final launch was at 13:32 on 2026-08-01, and the machine had been running for a full day after that. These numbers are closer to a normal desktop than a clean benchmark, which also makes the exact conditions hard to reproduce.
Even so, a 5.0x gap is too large to disappear into measurement noise. Both Python and Node ran natively on ARM64 Windows, and the gap remained. That is the straightforward conclusion from this data. The procedure is too coarse for claims about a few-percent difference.