On this page
How a Program Actually Runs: From Your Python to the CPU
What happens between pressing enter and the CPU doing work: bytecode, compilers, syscalls, the kernel, and why containers do not change any of it.
Quick answer
- Every language ends up as the same thing: machine instructions the CPU executes, one after another.
- Interpreted languages like Python and JavaScript run on a virtual machine that reads bytecode; compiled languages like Go and Rust are translated to native instructions up front.
- Your program cannot touch hardware directly. It asks the kernel through syscalls, and each one is a relatively expensive crossing.
- Stack memory is fast and automatically freed when a function returns; heap memory is flexible and lives until something frees it, which is where leaks come from.
- Containers and virtual machines do not change the execution model. They change which kernel answers your syscalls and what you are allowed to see.
From source code to instructions
There is only one thing a CPU does: execute instructions from its own instruction set. So whatever language you wrote in, something has to get it there. In compiled languages such as C, Rust, or Go, that translation happens ahead of time: a compiler turns your source into a native executable full of machine instructions for that architecture, and running the program hands those instructions straight to the CPU. The advantages are speed and a small runtime; the cost is a build step and a separate binary per platform.
In interpreted languages such as Python or JavaScript, the translation happens as the program runs. Python compiles your source to an intermediate form called bytecode, then a virtual machine executes that bytecode instruction by instruction. JavaScript engines go further and compile the hot parts to native code at runtime, a technique called just-in-time compilation. The model is friendlier and slower: each bytecode instruction costs several native ones, and the runtime has to be present. This is the honest reason a numerical loop in Python is often tens of times slower than the same loop in C, and why the numerical libraries you use are themselves written in C or Fortran with only a thin Python wrapper.
# Your Python is compiled - just to bytecode, not to machine code
python3 -c "import dis; dis.dis(lambda a, b: a * b + 1)"
# And that is why the fast path is a C library
python3 -c "import numpy; print(numpy.__file__)"What the CPU is doing while your code runs
A running program is two things: a set of instructions, and a block of memory holding its data. The CPU keeps a program counter pointing at the next instruction, fetches it, decodes what it means, executes it, and advances. When your code calls a function, the return address and local variables go on the stack — a region of memory that grows and shrinks in a strict last-in-first-out order, which is why it is cheap. When your code needs memory that outlives the function that created it, it asks for space on the heap instead, where allocation is slower and freeing is your responsibility or the runtime’s.
This division explains a class of bugs that look mysterious until you know the model. Infinite recursion exhausts the stack and produces a stack overflow, because the stack has a fixed size. A heap leak grows without bound and eventually triggers the out-of-memory killer. And an uninitialised variable holding a stale value is the debris of memory being reused. You do not need to think about any of this while writing ordinary application code, but knowing the two regions exist is what makes sense of the error messages when they appear.
The layers between your code and the hardware
Your program does not talk to hardware. It asks the kernel, through system calls (syscalls) — read this file, open a socket, allocate memory, start a process. A syscall is a controlled jump from unprivileged code into the kernel, and it costs far more than a normal function call: hundreds of nanoseconds to a few microseconds once you account for the mode switch and the work the kernel does. That is why a program making a thousand tiny file reads is dramatically slower than one making ten larger reads, and why batching and buffering are real optimisations rather than micro-tidying.
Above the syscalls sit the userspace layers you actually interact with. A runtime or virtual machine, the standard library, the language’s package manager, and the framework all stack up between your code and the kernel. Then containers add another distinction: they run on the same kernel as the host, isolated by kernel namespaces and cgroups, whereas a virtual machine runs its own kernel on emulated or passed-through hardware. That is why containers start in milliseconds and a VM takes seconds, and why a container cannot use a kernel feature the host does not have. ‘It works on my machine’ is almost always a statement about these layers: a different library version, a different kernel, or a service the other environment cannot reach.
Concurrency: one core, many tasks
A single CPU core executes one instruction stream at a time, but your programme needs to do several things at once — handle requests, poll a socket, run timers. Two strategies exist. Threads let the operating system interleave several instruction streams on the same core, switching when one blocks on I/O; this uses multiple cores when they are available but costs memory and creates the possibility of two threads corrupting the same data. An event loop takes the opposite approach: one thread, a queue of callbacks, and the rule that nothing may block, so that a single core can juggle thousands of connections. Asynchronous Python, Node.js, and Go’s goroutines are variations on these two ideas.
Which is right depends on the work. Tasks that spend their time waiting on a network or a disk — the vast majority of web application code — are served well by an event loop, because the CPU is idle while waiting anyway. Tasks that compute are not: they need real parallelism, which means multiple processes or multiple cores. This is why the answer to ‘should I use async?’ is about your workload and not about modernness, and why a CPU-bound job inside an async framework still blocks everything if it runs on the event loop.
Where this bites vibecoders
AI assistants generate code that treats every operation as equally cheap. A loop that opens a file, parses a line, and closes it; a database call inside a nested loop; a function that spawns a thread per item. Rendered as source, those look like ordinary code, because the cost is not visible in the syntax — it lives in the syscalls and the memory model underneath. Learning to read a loop and ask ‘how many kernel crossings and how much allocation does each iteration cost?’ is the skill that separates code that survives real data from code that works beautifully on ten sample rows.
Where AI coding assistants get this wrong
- Doing a file, network, or database operation once per loop iteration instead of in batches.
- Blocking the event loop with a CPU-heavy function inside async code.
- Reading files line by line with no buffering, turning one syscall into thousands.
- Assuming a container provides the same kernel and libraries as the development machine.
- Leaking heap memory by accumulating references in a long-lived collection.
- Recursing deeply enough to overflow the stack on large inputs.
Checklist
- Batch I/O instead of doing one operation per item in a loop.
- Keep CPU-heavy work off the event loop; use processes or worker threads.
- Buffer reads and writes rather than making many tiny syscalls.
- Check which library versions and kernel the deployment environment actually has.
- Bound any collection that grows with input, and free what you no longer need.
- Test with realistic data volumes, not a handful of sample rows.
FAQ
What is the difference between compiled and interpreted languages?
Compiled languages are translated into native machine instructions ahead of time, so running them hands instructions straight to the CPU. Interpreted languages are translated as they run, usually to an intermediate bytecode that a virtual machine executes. The line blurs — JavaScript compiles hot code at runtime — and both models end up as machine instructions.
Do containers make programs run differently?
No. The program is still bytecode or machine code executed by the same model. What changes is the environment around it: containers share the host kernel and are isolated with namespaces and cgroups, while virtual machines run their own kernel. That affects startup time, isolation, and which features are available, not how your code executes.
Why is Python slower than C?
Because each Python bytecode instruction costs several native instructions in the virtual machine, and because every value is a full object with type information rather than a fixed-size number. The escape hatch is to move the heavy work into a compiled library: this is exactly why numerical and machine-learning Python code is mostly a thin layer over C, C++, or GPU kernels.
Related topics
- Hardware for AI Builders: How the Machine Your Code Runs On Actually Works
- What Is a CPU (and Why More Cores Isn’t Always Faster)?
- What Is RAM (and Why Does Your App Run Out of Memory)?
- What Is a vCPU (and Why Isn’t It a Real Core)?
- What Is a GPU (and Why Is It Different From a CPU)?
- Bits, Bytes, and Bandwidth: The Units Behind Every Spec Sheet