How Computers Run Code: Memory, CPUs, and Execution
Why This Matters (The Magic Behind the Curtain)
Here’s the thing: you write x = 5 in Python or Java, and it just
works. But have you ever wondered what actually happens? Where does x
live? How does the computer know what to do with it? Why do some
programs run instantly while others crash with “out of memory” errors?
Understanding how computers execute code isn’t just academic—it’s the key to understanding memory leaks, stack overflow errors, why compiled languages are faster than interpreted ones, and how to write programs that don’t eat all your RAM.
Variables: Named Boxes in Memory
When you write:
1x = 42The computer doesn’t actually create a thing called “x.” What it does is:
- Allocate a chunk of memory (a few bytes)
- Store the value 42 in that memory
- Associate the name “x” with that memory address
Think of memory as a giant warehouse with millions of numbered boxes. Each box can hold a value. A variable name is just a label pointing to one of those boxes.
1Memory Address Value Variable Name
20x1000 42 x
30x1004 17 y
40x1008 99 zWhen you write print(x), the computer:
- Looks up where “x” points (address 0x1000)
- Goes to that address
- Reads the value (42)
- Prints it
In Python:
1x = 42
2print(id(x)) # Prints memory address (something like 140735268920304)
3
4y = x # y now points to the same memory location
5print(id(y)) # Same address!In Java:
1int x = 42; // Stored in memory
2int y = x; // y gets a copy of the value
3
4// For objects, it's different:
5String name = "Alice"; // name points to memory containing "Alice"
6String other = name; // other points to the same memoryTwo Kinds of Memory: Stack vs Heap
Your program uses two main areas of memory: the stack and the heap. Understanding the difference is crucial.
The Stack: Fast and Organized
The stack is like a stack of cafeteria trays. You can only add to the top (push) or remove from the top (pop). It’s fast and automatic.
The stack stores:
- Local variables (variables inside functions)
- Function call information (which function called which)
- Primitive values in Java (int, double, boolean)
Example:
1def calculate():
2 a = 5 # Stored on the stack
3 b = 10 # Stored on the stack
4 result = a + b # Stored on the stack
5 return result
6
7answer = calculate() # 'answer' stored on the stackWhen calculate() is called:
1Stack (growing downward):
2+-------------------+
3| answer (pending) | ← Main function's variables
4+-------------------+
5| result = 15 | ← calculate()'s variables
6| b = 10 |
7| a = 5 |
8+-------------------+When calculate() returns, its stack frame (a, b, result) is
automatically removed. This is why local variables disappear when a
function ends.
Stack Overflow: Too Many Function Calls
The stack has a fixed size. If you nest function calls too deeply (like infinite recursion), you run out of stack space.
Example of stack overflow:
1def infinite_recursion():
2 return infinite_recursion() # Calls itself forever
3
4infinite_recursion()
5# RecursionError: maximum recursion depth exceeded1public static void infiniteRecursion() {
2 infiniteRecursion(); // Calls itself forever
3}
4
5infiniteRecursion();
6// StackOverflowError!The Heap: Flexible but Manual
The heap is like a big, unorganized warehouse. You can allocate memory anywhere, any time. It’s slower than the stack but flexible.
The heap stores:
- Objects in Java (anything created with
new) - Dynamically allocated data (lists, arrays that grow)
- Long-lived data that needs to outlive a function
In Python:
1# Lists, dicts, objects are stored on the heap
2my_list = [1, 2, 3] # The list itself is on the heap
3 # 'my_list' (the variable) is on the stack
4 # but it points to heap memory
5
6def create_list():
7 data = [10, 20, 30] # Heap allocation
8 return data # Returns reference to heap memory
9
10result = create_list() # 'result' points to heap memory
11# The list [10, 20, 30] still exists even after create_list() returns!In Java:
1public class Person {
2 String name;
3 int age;
4}
5
6public Person createPerson() {
7 Person p = new Person(); // 'p' on stack, object on heap
8 p.name = "Alice";
9 p.age = 28;
10 return p; // Returns reference to heap object
11}
12
13Person alice = createPerson();
14// The Person object still exists on the heap!Stack vs Heap: The Key Differences
| Stack | Heap |
|---|---|
| Fast | Slower |
| Fixed size | Grows as needed |
| Automatic cleanup | Manual management |
| Local variables | Objects, dynamic data |
| LIFO (Last In, First Out) | Unorganized |
| Small (MB) | Large (GB) |
Memory Leaks: When Heap Memory Isn’t Freed
In languages like C, you manually allocate and free heap memory. If you forget to free it, you get a memory leak.
In C (manual memory management):
1int* numbers = malloc(1000 * sizeof(int)); // Allocate heap memory
2// ... use the memory ...
3free(numbers); // YOU must free it!
4// If you forget to free(), memory leak!
In Python and Java (automatic garbage collection):
Both use garbage collection—the runtime automatically frees memory that’s no longer referenced.
1# Python
2def create_big_list():
3 data = [i for i in range(1000000)] # Heap allocation
4 return None # Doesn't return the list
5
6create_big_list()
7# The list is no longer referenced, so the garbage collector
8# will automatically free that memory1// Java
2public void createBigArray() {
3 int[] data = new int[1000000]; // Heap allocation
4 // Method ends without returning it
5}
6
7createBigArray();
8// The array has no references, so the garbage collector
9// will eventually free itHow the CPU Executes Instructions
Your CPU is like a factory worker following a recipe. It doesn’t understand Python or Java—it only understands machine code: binary instructions.
The Fetch-Decode-Execute Cycle
Every CPU constantly does this:
- Fetch: Get the next instruction from memory
- Decode: Figure out what the instruction means
- Execute: Perform the operation
- Repeat: Go to step 1
Example: Adding Two Numbers
Your code:
1x = 5
2y = 10
3z = x + yThe CPU sees (simplified machine code):
1LOAD 5 into Register A // Fetch instruction, decode, execute
2LOAD 10 into Register B // Fetch instruction, decode, execute
3ADD A and B, store in C // Fetch instruction, decode, execute
4STORE C to memory // Fetch instruction, decode, executeRegisters are tiny, super-fast memory locations inside the CPU (think: the chef’s hands). The CPU loads values from RAM into registers, does math on them, and stores results back to RAM.
Why This Matters: Speed
- Register access: ~1 nanosecond (fastest)
- Cache access: ~10 nanoseconds
- RAM access: ~100 nanoseconds
- SSD access: ~100,000 nanoseconds
- Hard drive access: ~10,000,000 nanoseconds
This is why accessing a variable in memory is fast, but reading from disk is slow.
From Code to Execution: Compilation vs Interpretation
How does your Python or Java code become machine code the CPU can run? There are two main approaches.
Compiled Languages: Direct Translation
Compiled languages (C, C++, Rust, Go) convert your code to machine code before you run it.
1Your Code → Compiler → Machine Code (executable) → CPU runs itExample: C compilation
1// hello.c
2#include <stdio.h>
3
4int main() {
5 printf("Hello, world!\n");
6 return 0;
7}Compile it:
1gcc hello.c -o hello # Compiler creates executable
2./hello # CPU runs machine code directly
3# Output: Hello, world!Pros:
- Fast: Direct machine code, no translation overhead
- Optimized: Compiler can optimize for your CPU
Cons:
- Platform-specific: Must recompile for different CPUs (Windows vs Mac vs Linux)
- Slower development: Must compile before testing changes
Interpreted Languages: Real-Time Translation
Interpreted languages (Python, JavaScript, Ruby) translate and run your code line by line at runtime.
1Your Code → Interpreter reads and executes line by line → CPUExample: Python interpretation
1# hello.py
2print("Hello, world!")Run it:
1python hello.py # Interpreter reads, translates, and executes
2# Output: Hello, world!Pros:
- Cross-platform: Same code runs anywhere with an interpreter
- Fast development: No compilation step, just run it
Cons:
- Slower: Translation happens at runtime
- Less optimized: Interpreter can’t optimize as much
Java: The Best of Both Worlds (Sort Of)
Java uses a hybrid approach.
1Your Code (.java) → Compiler → Bytecode (.class) → JVM interprets
2 (or JIT compiles) → CPUSteps:
- Compile to bytecode:
javac Hello.javacreatesHello.class - JVM runs bytecode:
java Helloruns it
Key insight: Java compiles to bytecode (not machine code). Bytecode is platform-independent. The JVM (Java Virtual Machine) then interprets or JIT-compiles it to machine code.
Example:
1// Hello.java
2public class Hello {
3 public static void main(String[] args) {
4 System.out.println("Hello, world!");
5 }
6}Compile and run:
1javac Hello.java # Creates Hello.class (bytecode)
2java Hello # JVM runs the bytecode
3# Output: Hello, world!JIT Compilation (Just-In-Time):
Modern JVMs use JIT compilation: they analyze running code and compile hot paths (frequently executed code) to native machine code for speed.
So Java starts interpreted (slow) but speeds up as the JIT compiler optimizes (fast).
Python: Bytecode Too
Python also compiles to bytecode (.pyc files), but you usually don’t see it. The interpreter handles it automatically.
1python hello.py
2# Behind the scenes:
3# 1. Compile hello.py to bytecode
4# 2. Interpret bytecodeWhy Python is slower than Java:
- Python’s interpreter is simpler (no JIT in standard Python)
- Dynamic typing means more runtime checks
- No aggressive optimization
(PyPy, an alternative Python interpreter, uses JIT compilation and is much faster.)
Putting It All Together: What Happens When You Run Code
Let’s trace what happens when you run this Java program:
1public class Example {
2 public static void main(String[] args) {
3 int x = 5;
4 int y = 10;
5 int sum = x + y;
6 System.out.println(sum);
7 }
8}Step by step:
Compilation (javac Example.java):
- Compiler reads your source code
- Checks for syntax errors
- Converts to bytecode (Example.class)
- Bytecode is platform-independent instructions
Execution (java Example):
- JVM loads Example.class into memory
- Creates stack for main() function
- Allocates stack variables: x, y, sum
- Executes bytecode:
- LOAD 5 into x (stack)
- LOAD 10 into y (stack)
- ADD x + y, store in sum (stack)
- CALL System.out.println with sum
- Prints 15 to console
- Cleans up stack when main() ends
- JVM exits
For Python:
1x = 5
2y = 10
3sum = x + y
4print(sum)- Interpretation (python script.py):
- Interpreter reads line 1:
x = 5 - Compiles to bytecode (in memory)
- Executes: Allocate heap memory, store 5, bind name “x”
- Interpreter reads line 2:
y = 10 - Same process for y
- Interpreter reads line 3:
sum = x + y - Executes: Load x, load y, add, store result as sum
- Interpreter reads line 4:
print(sum) - Executes: Load sum, call print function
- Prints 15
- Garbage collector eventually frees x, y, sum from heap
- Interpreter reads line 1:
Real-World War Story
Let me tell you about a memory leak that brought down our production server. We had a Java web application that processed user uploads. The code looked innocent:
1public void processUpload(File file) {
2 List<String> lines = new ArrayList<>();
3 BufferedReader reader = new BufferedReader(new FileReader(file));
4
5 String line;
6 while ((line = reader.readLine()) != null) {
7 lines.add(line); // Storing every line in memory!
8 }
9
10 // Process lines...
11}The problem: For large files (1 GB+), we stored every line in memory (the heap). With multiple users uploading simultaneously, we ran out of heap space.
The fix: Process line by line without storing everything:
1public void processUpload(File file) throws IOException {
2 try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
3 String line;
4 while ((line = reader.readLine()) != null) {
5 processLine(line); // Process immediately, don't store
6 // Old line is no longer referenced, GC can free it
7 }
8 }
9}The lesson: Understand stack vs heap. Don’t load everything into memory if you can process it incrementally.
What You Need to Remember
Here’s what I wish someone had told me when I was learning this:
- Variables are memory addresses - names pointing to boxes in RAM
- Stack is fast, automatic, limited - local variables live here
- Heap is flexible, manual, unlimited - objects and dynamic data live here
- Stack overflow = too many function calls - usually infinite recursion
- Memory leak = heap memory not freed - garbage collection helps but isn’t perfect
- CPU executes machine code - fetch, decode, execute cycle
- Compiled languages are fast - direct machine code
- Interpreted languages are flexible - platform-independent
- Java uses both - compiles to bytecode, JVM interprets/JIT compiles
How This Helps Your Career
Understanding how code executes shows up everywhere:
- Debugging crashes - “StackOverflowError? Must be infinite recursion”
- Performance optimization - “Don’t load the whole file into memory!”
- Choosing languages - When to use Python vs Java vs Go
- Understanding frameworks - Why does Spring Boot take so long to start? (JVM warmup)
- System design - How much memory will this service need?
Six months from now, when you see “OutOfMemoryError: Java heap space,” you’ll immediately think “we’re creating too many objects on the heap” and know how to fix it. When a recursive function crashes, you’ll recognize stack overflow.
Trust me, this mental model of how computers actually run your code is one of those foundational concepts that makes everything else make sense. Learn it once, apply it forever.
Now go forth and write code that understands memory!