# What Actually Happens When Your Code Runs

> The journey from the text you type to a living program: how source code gets translated into machine instructions, where your data lives while it runs, and what 'running' actually means to the operating system and CPU.


---

# What Actually Happens When Your Code Runs

You can write a little code. You've typed something, pressed Run, and watched it work - or watched it break - without ever being told what happened in between. The file you wrote is just text. The machine in front of you doesn't read English, or Python, or anything that looks like what you typed. So how does a page of words become a thing that *does something*?

That gap - between the code you write and the machine that runs it - is where a lot of programming feels like magic or superstition. "Use Go because it's compiled." "Python is slow because it's interpreted." "Watch out for stack overflow." These are real, knowable things, and once you can picture the whole chain - text → translated → running program → CPU - they stop being spells and start being something you can reason about.

This guide walks that chain end to end, in plain language, with pictures.

## How to read this
- **Want the big picture fast?** Read [Phase 1](01-source-to-machine.md) - it's the heart of it: how your text becomes something the machine can run.
- **Want it to finally make sense?** Read in order. Each phase is one link in the chain, and they connect: how code is *translated*, then *where its data lives* while running, then what *"running"* even means.

## The phases
1. **[From Source Code to Something the Machine Runs](01-source-to-machine.md)** - your code is text; the CPU runs machine instructions. How a compiler (translate-ahead) and an interpreter (translate-as-you-go) bridge that gap, and the trade-off between them.
2. **[Where Your Data Lives: the Stack & the Heap](02-stack-and-heap.md)** - when code runs, every value sits somewhere in memory. The stack (fast, automatic, for function calls) versus the heap (flexible, for things that outlive a function) - and what "stack overflow" really is.
3. **[What "Running" Means](03-what-running-means.md)** - your program becomes a *process* the operating system schedules onto the CPU, using RAM as its workspace. We tie the whole chain together.

> Deeper material lives in neighboring guides: how the OS juggles many running programs at once is [Processes, Memory & the CPU](/guides/processes-memory-and-cpu); how the hardware itself is laid out is [CPU, RAM & Storage](/guides/cpu-ram-and-storage); and how a program's memory gets cleaned up afterward is [Memory & Garbage Collection](/guides/memory-and-garbage-collection). Brand new to writing code at all? Start with [Programming from Zero](/guides/programming-from-zero).


---

# From Source Code to Something the Machine Runs

Open one of your program files in a text editor. It's text - letters, numbers, brackets, indentation. You could print it on paper. Nothing about it physically *does* anything; it's a description, written for a human to read and a machine to be told about.

Here's the gap nobody points out: **the CPU - the chip actually doing the work - cannot read that text.** It has no idea what `print`, `if`, or `def` mean. The CPU understands one thing: a stream of extremely simple, numeric instructions ("add these two numbers," "copy this value here," "jump to that spot"). That's its entire vocabulary. So *something* has to stand between the words you wrote and the instructions the chip can run. This phase is about what that something is.

## What your code actually is, and what the machine actually runs

**What source code actually is.** Your `.py`, `.js`, `.go`, or `.c` file is **source code**: text written in a programming language, designed to be readable by people. It's the *source* - the original, human-friendly description of what you want to happen.

📝 **Terminology.** *Source code* = the human-readable text you write. *Machine code* = the raw numeric instructions a specific CPU can execute directly. They are two completely different things; one has to become the other before anything runs.

**What the machine runs.** The CPU runs **machine code**: a long sequence of tiny instructions, encoded as numbers, that match that exact kind of chip. Machine code is not readable in any practical sense - it's the opposite end of the spectrum from your source. Here's the contrast, made concrete:

```mermaid
flowchart LR
  Src["source code<br/>total = price + tax"] --> Trans["translate"]
  Trans --> M1["load price → reg A"]
  Trans --> M2["load tax → reg B"]
  Trans --> M3["add A, B → reg A"]
  Trans --> M4["store reg A → total"]
```

One readable line on the left can become several machine instructions on the right. The translation from one to the other is the job we're about to meet, and there are two ways to do it.

💡 **Key point.** Every program, in every language, has to cross this same gap: from human-readable source to machine-runnable instructions. The whole "compiled vs. interpreted" debate is just a debate about *when* and *how* that crossing happens.

## The two ways across the gap

There are two strategies for turning your source code into something that runs, easiest understood by analogy: imagine you wrote a book in English and need it read aloud to a French-speaking audience.

- **Strategy one: translate the whole book ahead of time.** A translator converts the entire book into French and prints it. Later, anyone can read the French copy aloud, fast, as many times as they like - the translation work is already done. This is **compiling**.
- **Strategy two: bring a live interpreter to the reading.** No French copy exists. As you read each English sentence, the interpreter speaks the French version on the spot. It works immediately, with no upfront print job - but the interpreter has to be present every time, translating as you go. This is **interpreting**.

Both get the story to the French audience. They just pay the translation cost at different moments.

## The compiler: translate-ahead

**What it actually is.** A **compiler** is a program that takes your *entire* source file and translates it, all at once, *before* the program ever runs, into machine code (or something close to it). The output is a separate, ready-to-run file - an **executable**.

📝 **Terminology.** A *compiler* translates whole source code into machine code ahead of time. An *executable* (a `.exe` on Windows, or a plain binary on macOS/Linux) is the resulting file of machine code you can run directly.

Languages that work this way include **Go, Rust, and C**.

**What it does in real life.** You run the compiler once. It chews through your code, reports any errors it finds, and - if all is well - hands you an executable. From then on, running your program means running *that file*; the compiler isn't involved.

```console
$ go build hello.go
$ ls
hello       hello.go
$ ./hello
Hello, world!
```
*What just happened:* `go build` was the compiler. It read your source file `hello.go` and produced a new file, `hello` - the executable, a bundle of machine code for your kind of computer. Running `./hello` ran *that file* directly on the CPU. The `.go` source wasn't consulted at all; the translation already happened during `go build`.

**The trade-off.** Because the translation is done up front, the program starts and runs fast - there's no translating while it runs. The cost is paid earlier: you have to compile before you can run (a step that takes time on big projects), and the executable is built for one kind of machine, so a binary compiled for Windows won't run on a Mac.

## The interpreter: translate-as-you-go

**What it actually is.** An **interpreter** is a program that reads your source code and runs it *directly*, working through it piece by piece, translating-and-executing as it goes. There's no separate executable produced ahead of time - the interpreter is doing the translation live, every time the program runs.

📝 **Terminology.** An *interpreter* reads and executes source code on the fly, a piece at a time, rather than translating the whole thing into a standalone executable first.

Languages commonly run this way include **Python and JavaScript**.

**What it does in real life.** Hand your source file straight to the interpreter and it starts running immediately - no build step.

```console
$ python hello.py
Hello, world!
```
*What just happened:* `python` is the interpreter. It read `hello.py` and ran it on the spot - figuring out what each line means and doing it, line by line, right then. Nothing was pre-translated into a separate machine-code file; the interpreter stayed in charge the whole time the program ran. That's why `python` has to be installed for the program to run at all.

**The trade-off.** You get to run code instantly - edit, run, edit, run, with no waiting on a build. That fast feedback loop is a real pleasure. The cost is that the translating happens *while the program runs*, over and over, so the same work tends to run slower than the compiled version, where translation was done once, in advance.

⚠️ **Gotcha - "compiled" and "interpreted" describe how a language is *usually run*, not a law of nature.** The same language can often be run either way, and many modern languages blur the line (some interpreters compile hot code to machine instructions while running, a technique called just-in-time compilation). So when someone says "Python is interpreted," hear it as "Python is normally run by an interpreter," not "Python can only ever be interpreted." Don't over-trust the label.

## The straight comparison

Neither approach is "better" - they're optimized for different moments. Here's both sides, plainly:

```text
                      COMPILED (translate-ahead)      INTERPRETED (translate-as-you-go)
                      e.g. Go, Rust, C                e.g. Python, JavaScript
   ──────────────────────────────────────────────────────────────────────────────────
   When it translates   once, before running          continuously, while running
   Build step?          yes - compile first           no - run the source directly
   Startup / run speed   typically faster              typically slower
   Feedback loop        slower (wait to build)         fast (edit, run, repeat)
   What you ship        an executable (machine code)   the source + an interpreter
   Runs where?          one CPU/OS it was built for    anywhere the interpreter exists
```

The single thing driving every row is *when the translation happens*. Pull that thread and the rest follows.

🪖 **War story.** A teammate switched a small data script from Python to Go "for the speed" and was baffled when, for their tiny one-second job, it didn't feel faster to *use* - they'd added a compile step to their workflow and the script was too short for the runtime speed-up to matter. Compiled-vs-interpreted isn't "fast vs slow" in the abstract; it's a trade about *where the time goes*. For a quick script you run once, the interpreter's instant start often wins; for a program that runs hard for hours, the compiler's upfront work pays off.

## Recap

1. Your **source code** is human-readable text. The **CPU** only runs **machine code** - tiny numeric instructions. Something must translate one into the other.
2. A **compiler** translates the *whole* program ahead of time into an **executable**, then steps out of the way (Go, Rust, C).
3. An **interpreter** translates *and runs* your source as it goes, every time, with no separate executable (Python, JavaScript).
4. The trade-off is all about **when** translation happens: compiled gives faster runs but needs a build step; interpreted gives instant feedback but does its translating live.
5. "Compiled" / "interpreted" describe how a language is *usually run*, not an unbreakable rule.

Now you know how your text becomes runnable instructions. Next question: once those instructions start running, the values they work with - your numbers, your text, your lists - have to *live* somewhere. Let's look at where.


---

# Where Your Data Lives: the Stack & the Heap

In the last phase, your code became machine instructions that the CPU runs. But those instructions are constantly working with *values* - a number you added, a name you stored, a list you built. Each value has to physically sit somewhere while the program runs, and that somewhere is your computer's memory (RAM).

Here's the part almost never explained to beginners: that memory isn't one undifferentiated blob. While your program runs, it organizes its data into two distinct regions with two different personalities - the **stack** and the **heap**. You don't usually manage them by hand (in most languages the system handles it for you), but knowing they exist explains a whole category of behavior and a famous crash.

## The two regions, side by side

Picture the memory your running program is handed. Two areas matter here:

```mermaid
flowchart TD
  subgraph Stack["THE STACK - fast, automatic, tied to calls"]
    direction TB
    F2["greet() ← current call<br/>name = 'Sam'"] --> F1["main()"]
  end
  subgraph Heap["THE HEAP - flexible, longer-lived"]
    direction TB
    H1["a big list of 10,000 items"]
    H2["an image loaded from disk"]
  end
  Stack -.- Heap
```

They solve two different problems. The stack is for the short-lived, predictable bookkeeping of function calls. The heap is for everything that needs to stick around or grow. Let's take them one at a time.

## The stack: fast, automatic, tied to function calls

**What it actually is.** The **stack** is a region of memory that grows and shrinks in a strict, orderly way as your functions call each other. Every time a function is called, the program adds a little block on top of the stack - a **stack frame** - to hold that function's local variables. When the function finishes and returns, its frame is removed, instantly and automatically.

📝 **Terminology.** A *stack frame* is the chunk of stack memory belonging to one function call: its local variables and the bookkeeping needed to return to whoever called it. "Stack" is the right word - like a stack of plates, you add and remove only from the top.

**What it does in real life.** Consider this little chain of calls:

```python runnable
def greet(name):
    message = "Hi " + name   # `name` and `message` live in greet's stack frame
    return message

def main():
    user = "Sam"             # `user` lives in main's stack frame
    print(greet(user))

main()
```

As this runs, the stack changes shape:

```mermaid
flowchart LR
  subgraph s1["step 1: main() called"]
    a1["main()<br/>user = 'Sam'"]
  end
  subgraph s2["step 2: main() calls greet()"]
    direction TB
    b2["greet() ← added on top<br/>name = 'Sam'"] --> b1["main()<br/>user = 'Sam'"]
  end
  subgraph s3["step 3: greet() returns"]
    c1["main()<br/>user = 'Sam'<br/>(greet's frame gone)"]
  end
  s1 --> s2 --> s3
```
*What just happened:* Calling `greet` pushed a new frame on top of the stack, holding its local variables (`name`, `message`). The moment `greet` returned, that whole frame was discarded automatically - no cleanup code from you. That automatic add-on-call, remove-on-return is why the stack is fast and why you never have to think about freeing a local variable.

💡 **Key point.** Stack memory is *automatic*: a local variable lives exactly as long as its function call, then vanishes on its own when the function returns. You get this for free.

## The heap: flexible, for things that outlive a function

**What it actually is.** The **heap** is a separate region for data that can't follow the tidy add-and-remove-from-the-top rhythm of the stack - usually because it needs to **outlive** the function that created it, or because its size isn't known in advance, or it's large.

📝 **Terminology.** The *heap* is the region of memory used for longer-lived or dynamically-sized data. Unlike the stack, items here don't disappear just because a function returned - they persist until something decides they're no longer needed.

**What it does in real life.** Suppose a function builds a list and hands it back to its caller:

```python
def load_scores():
    scores = [10, 20, 30, ... ]   # a list, big and dynamic → lives on the heap
    return scores                 # the list survives after load_scores returns

results = load_scores()           # `results` now uses that same heap data
```
*What just happened:* The list can't live on `load_scores`'s stack frame, because that frame is destroyed the instant the function returns - yet the caller still needs the list. So the list lives on the **heap**, which outlasts the function call. The function returns a reference to it, and `results` uses it afterward. The heap is what makes "create something here, use it over there, later" possible.

**The trade-off.** The heap's flexibility has a cost: nothing automatically removes heap data when a function returns. *Something* has to decide when heap data is finished with and reclaim that space, or the program slowly eats memory it can never get back. In many languages (Python, JavaScript, Go, Java) an automatic **garbage collector** handles this for you; in others (C, Rust) it's managed differently. How heap memory gets cleaned up is its own guide: [Memory & Garbage Collection](/guides/memory-and-garbage-collection).

## ⚠️ "Stack overflow" - a real, specific thing

You've seen the name on a famous website, but **stack overflow** is a genuine error with a precise cause, and the stack you just learned about is exactly what overflows.

The stack is fast partly because it's *limited in size* - the system sets aside a fixed, fairly small amount of memory for it. Every function call adds a frame on top, so what happens if functions keep calling, deeper and deeper, without ever returning? The frames pile up... and eventually run past the edge of the space reserved for the stack. That's a **stack overflow**: the stack grew beyond its limit, and the program is killed on the spot.

The classic way to cause it is **recursion** - a function that calls itself - with no stopping condition:

```python
def countdown(n):
    print(n)
    countdown(n - 1)   # calls itself forever - no base case to stop it

countdown(5)
```

```console
$ python countdown.py
5
4
3
...
-9994
-9995
  File "countdown.py", line 3, in countdown
    countdown(n - 1)
RecursionError: maximum recursion depth exceeded
```
*What just happened:* Each call to `countdown` added another stack frame, and the function never returned, so the frames never got removed - they just kept piling up. Once they crossed the stack's size limit, the program stopped with a recursion/stack error. (Python raises a clean `RecursionError`; lower-level languages like C may crash harder with a literal "stack overflow.") The data wasn't corrupted - the program ran *out of stack room*.

⚠️ **Gotcha.** The fix for a stack overflow from recursion is almost never "ask for a bigger stack." It's to make sure recursion actually *stops* - every self-calling function needs a **base case**, a condition where it returns instead of calling itself again. Here, `countdown` should stop at, say, `if n < 0: return`. Bottomless recursion is the cause; a proper stopping point is the cure.

## Recap

1. While your program runs, every value lives in memory - and that memory is organized into two regions, the **stack** and the **heap**.
2. The **stack** holds each function call's local variables in a **stack frame**, added on call and removed on return - automatic, orderly, and fast.
3. The **heap** holds data that must **outlive** the function that made it, or that's large or dynamically sized - flexible, but it must be cleaned up rather than vanishing on its own.
4. **Stack overflow** is a real error: function calls pile up frames faster than they're removed (classically, bottomless recursion) until the stack exceeds its size limit and the program dies.
5. The cure for runaway recursion is a **base case** that lets it stop - not a bigger stack.

You now know how code is translated, and where its data lives while it runs. The last piece is the one wrapping all of it: what does it actually *mean* for a program to be "running"? Let's connect the whole chain.

Watch it animated: [the stack and the heap](/explainers/StackHeap.dc.html)


---

# What "Running" Means

We've followed your code a long way. It started as text. A compiler or interpreter turned it into machine instructions. Those instructions work with data that lives on the stack and the heap. But there's still a word we've been using loosely the whole time: **running**. When you double-click an app or type a command and hit Enter, what *is* the thing that comes to life? Let's give "running" a real definition and snap every piece into place.

## A running program is a process

**What it actually is.** When you start a program, the operating system takes your executable (or fires up the interpreter on your source) and creates a **process**: a living instance of that program, loaded into memory and being executed. The file on disk is *potential*; the process is the program *actually happening*.

📝 **Terminology.** A *process* is a program in the act of running - its instructions loaded into RAM, its stack and heap set up, and an identity the operating system tracks. The same program can run as several processes at once (think two separate browser windows from one app).

The difference is worth holding onto: your executable is like a recipe sitting in a cookbook. A process is what exists when someone is *actually cooking it* - ingredients out, pots on the stove, the kitchen busy. One recipe, but you could cook it twice at once in two kitchens.

```mermaid
flowchart LR
  Disk["ON DISK (resting)<br/>hello - just a file"] -->|you run it| Proc["IN RAM (running)<br/>PROCESS: hello<br/>machine code · stack · heap · PID"]
```

## RAM is the workspace; the CPU does the work

Two pieces of hardware do the heavy lifting for a running process, and they have clearly different jobs.

**RAM is where the process lives while it runs.** Its machine instructions, its stack, its heap - all of it sits in **RAM** (random-access memory), the computer's fast working memory. This is *why* a program has to be loaded before it runs: it's copied from slow long-term storage (your disk) into fast working memory where the CPU can reach it quickly. The difference between RAM and disk, and why it matters so much, is its own topic: [CPU, RAM & Storage](/guides/cpu-ram-and-storage).

**The CPU is what actually executes the instructions.** The **CPU** (the processor) is the part that does the work: it reads your process's machine instructions from RAM, one after another, and carries each one out - add these, copy that, compare, jump. This is the literal meaning of "the code is running": the CPU is stepping through your translated instructions, in order, doing exactly what each one says.

```mermaid
flowchart LR
  RAM["RAM<br/>your process lives here:<br/>code, stack, heap"] -->|reads instructions| CPU["CPU<br/>executes one step at a time"]
  CPU <-->|reads & writes data| RAM
```

## The OS schedules your process onto the CPU

Here's the fact that surprises people: your process is almost never running *continuously*. Your computer has dozens - often hundreds - of processes alive at once, but only a handful of CPU cores to run them on, so they can't all run at the same instant.

**What the OS does in real life.** The operating system acts as a scheduler. It gives your process a slice of the CPU, lets it run for a tiny moment, then pauses it and hands the CPU to another process, and another - cycling through all of them so fast it *looks* like everything runs at once. Your program experiences this as "running," even though, zoomed in, it's running in rapid bursts with pauses in between.

```mermaid
flowchart LR
  A["your program"] --> B["browser"] --> C["music app"] --> D["your program"] --> E["system task"] --> F["your program"]
```

*Over a few milliseconds, one CPU core hands its time around in quick slices - your process gets the CPU in bursts, not all at once.*

This juggling act - how the OS decides who runs when, and how to read a machine that feels "stuck" - is a rich topic on its own. When you're ready to go deeper into processes, scheduling, and what "100% CPU" really means, that's [Processes, Memory & the CPU](/guides/processes-memory-and-cpu).

💡 **Key point.** "Running" doesn't mean your program owns the machine. It means the OS has made it a **process** and is repeatedly scheduling it onto the **CPU** for short turns, while the process's code and data sit in **RAM**.

## The whole chain, in one picture

Now every piece from this guide connects. From the text you typed to the work the chip does:

```mermaid
flowchart LR
  S["1. SOURCE CODE<br/>total = price + tax"] -->|compiler / interpreter| T["2. TRANSLATED<br/>machine instructions"]
  T --> P["3. A PROCESS<br/>code, stack, heap in RAM<br/>scheduled by the OS"]
  P --> X["4. CPU EXECUTES<br/>add, copy, compare, jump<br/>step by step"]
```

Read left to right, that's the answer to the question this whole guide asked. **Source code** is text you write. A **compiler or interpreter** translates it into **machine instructions** ([Phase 1](01-source-to-machine.md)). To run, those instructions and their data are loaded into **RAM**, organized into **stack and heap** ([Phase 2](02-stack-and-heap.md)), as a **process** the OS **schedules onto the CPU**, which **executes** them one step at a time. No magic anywhere in the line - just a handoff from human-readable words to a chip doing simple things very fast.

⚠️ **Gotcha - "my program finished" doesn't mean its memory cleaned itself up mid-run.** While a process runs, the heap memory it allocates isn't automatically reclaimed the way stack frames are. If long-running programs keep grabbing heap memory and never letting go, they slowly consume RAM - a *memory leak*. How memory gets reclaimed (and the garbage collectors that automate it) is the natural next step: [Memory & Garbage Collection](/guides/memory-and-garbage-collection).

## Recap

1. A **process** is a program *actually running* - loaded into memory and being executed - as opposed to the resting executable file on disk.
2. While running, a process lives in **RAM** (its code, stack, and heap), because that's the fast working memory the CPU can reach quickly.
3. The **CPU** does the real work: it reads the process's machine instructions from RAM and executes them one step at a time.
4. The **OS schedules** your process onto the CPU in rapid slices, sharing the chip among the many processes alive at once - which is what "running" actually feels like from the outside.
5. The full chain: **source code → translated to machine instructions → loaded as a process into RAM → executed step by step by the CPU.**

That's the whole journey, end to end. From here, three neighbors go deeper into single links of the chain: [Processes, Memory & the CPU](/guides/processes-memory-and-cpu) for how the OS juggles running programs, [CPU, RAM & Storage](/guides/cpu-ram-and-storage) for the hardware underneath, and [Memory & Garbage Collection](/guides/memory-and-garbage-collection) for how a running program's memory is cleaned up.
