Part 1 — What Actually Happens When Code Runs
When we write: const result = add(10, 20); it feels like the computer simply "runs the code." But the CPU doesn't understand JavaScript. There are several layers between the code we write and the hardware actually executing instructions. That's what I wanted to understand first. From JavaScript to the CPU In Node.js, JavaScript is handled by V8, the JavaScript engine. A simplified view looks like this: JavaScript ↓ V8 ↓ Bytecode ↓ JIT compilation ↓ Machine instructions ↓ CPU V8 doesn't simply "interpret JavaScript" or "compile JavaScript" once and forget about it. It can start with bytecode and progressively compile frequently executed ("hot") code into more optimized machine code. Eventually, the CPU is executing instructions that operate at a much lower level than the JavaScript we originally wrote. What does the CPU actually do? At its core, a CPU repeatedly executes instructions. A simplified mental model is: Fetch → Decode → Execute → Repeat The CPU has several important pieces involved in this process. Registers are tiny, extremely fast storage locations inside the CPU. They're used to hold values the CPU is actively working with. The ALU (Arithmetic Logic Unit) performs many arithmetic and logical operations. The Program Counter (PC) keeps track of where the next instruction comes from. And the CPU runs according to a clock, measured in GHz. A 3 GHz CPU has roughly 3 billion clock cycles per second, but that does not mean it executes 3 billion instructions per second. Different instructions and architectures have different costs. Modern CPUs are far more sophisticated than this simplified model, using pipelining, multiple execution units, branch prediction, out-of-order execution, and more. But the basic model is enough to start reasoning about performance. The CPU doesn't get everything from RAM One of the most important things I learned here is that where data lives matters. A simplified hierarchy looks like: Registers ↓ L1 Cache ↓ L2 Cache ↓ L3 Cache ↓ RAM ↓ SSD ↓ Network This isn't a literal pipeline that every access travels through. It's a hierarchy of increasingly larger but generally slower resources. Registers are extremely close to the execution units and very limited in size. Caches sit between the CPU and RAM and keep frequently or recently used data closer to the CPU. RAM is much larger, but accessing it takes significantly more time than accessing a register or cache. This leads to a fundamental performance idea: The closer the data is to the CPU, the cheaper it generally is to access. Cache hits, misses and locality Suppose the CPU needs some data. If it's already in a cache: CPU → Cache → Data found that's a cache hit. If it isn't: CPU → L1 → L2 → L3 → RAM the CPU may have to go farther to find it. That's a cache miss. This is why locality matters. Temporal locality: if we accessed something recently, we're likely to access it again. Spatial locality: if we accessed one memory location, we're likely to access nearby locations. For example: for (let i = 0; i < array.length; i++) { sum += array[i]; } accesses elements sequentially. That pattern is generally friendlier to caches than jumping randomly around memory. This is also why two pieces of code with the same O(n) complexity can have very different real-world performance. Latency vs Bandwidth These two terms show up everywhere in computer systems. Latency is essentially: How long until I get the result? Bandwidth is: How much data can I transfer per unit of time? A system can have high bandwidth but still have significant latency. We'll encounter this distinction again with RAM, disks, networks, and databases. What about numbers? At the lowest level, computers work with bits: 0 or 1 Eight bits make a byte. A fixed number of bits means a fixed number of representable values. That's why we have concepts like: signed vs unsigned integers integer overflow limited precision Floating-point numbers introduce another interesting problem. JavaScript's Number uses IEEE-754 double-precision floating-point representation. Not every decimal fraction has an exact binary representation. That's why: 0.1 + 0.2 produces: 0.30000000000000004 It's not that JavaScript can't do basic addition. We're seeing the consequences of how numbers are represented in binary with finite precision. CPU-bound vs I/O-bound Another useful distinction is whether our program is primarily computing or waiting. A huge calculation: for (let i = 0; i < 5_000_000_000; i++) { // heavy computation } is CPU-bound. The CPU spends most of its time doing work. But something like: await fetch("/users"); is largely I/O-bound. The program may spend significant time waiting for the network rather than continuously using the CPU. This distinction becomes particularly important when we get to Node.js and its event loop. Putting it all together So when I run a Node.js function, the mental model I want to have is no longer: JavaScript → magic → result but something closer to: JavaScript ↓ V8 ↓ Bytecode / JIT compilation ↓ Machine instructions ↓ CPU core ↓ Registers ↓ Caches ↓ RAM Along the way, performance can be affected by things like: instruction execution, CPU utilization, memory latency, cache misses, locality, and I/O waits. And that's the foundation for everything that follows. Because the next question is obvious: If multiple programs are running on the same machine, who decides which program gets the CPU, how memory is managed, and how programs interact with hardware? That's where the Operating System comes in.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to