00From the metal up
You came here to write Python, and we will — but not on page one. First I want to open the box. Python feels like magic right up until you can see what's under it. Then it feels like machinery instead, which is far better, because machinery you can reason about. Here's the plan. We start at a single switch, the transistor that's either on or off, and we climb from there. One bit becomes a byte, bytes become RAM, and RAM feeds the CPU — the only part of the whole box that actually does anything. The whole way up we keep asking the one thing most tutorials skip right past: what really happens between saving a .py file and the instant it runs? By the end you'll read a binary number cold, picture memory as a street of numbered boxes, and trace your own code all the way down to the metal. Everything in the next fourteen chapters stands on this floor.
The rest of this chapter opens the machine. Before we do that, let us put something in your hands, because a promise you can feel beats a promise you have to take on trust. What follows takes about ten minutes and needs nothing you do not already own. You will not understand every character you type, and that is not a problem to be fixed — it is the arrangement. Right now we are after exactly one thing: the feeling of the machine answering you. Understanding what it answered with, and why, is what the next fourteen chapters are for.
First, open the prompt. One honest line for each kind of machine:
terminal, press Enter. In the window that opens, type py and press Enter.terminal, press Enter. In the window that opens, type python3 and press Enter.You know it worked when the last line in the window is three angle brackets and a space: >>>. That is Python, sitting there waiting for you to say something. Everything below gets typed at that prompt, one line at a time, pressing Enter after each.
>>>. Use it to start today. But install the real one when you can, and soon — a shell in a browser tab cannot see your files, and from Chapter 15 onward we work with files constantly. The browser wins the next ten minutes; the install wins the next fourteen chapters.>>> 2 + 2 4 >>> 2 ** 100 1267650600228229401496703205376
** means to the power of, so you asked for 2 multiplied by itself a hundred times. Most pocket calculators run out at about ten digits and fall back on a rounded 1.267650600e30. Python printed the whole number — thirty-one digits, every one of them exact, no rounding anywhere. You have been programming for about fifteen seconds.>>> "your name " * 3 'your name your name your name '
* did the sensible thing and repeated it — trailing space and all, three times over, exactly as written. Put your own name between those quotes and run it again. The machine is not replaying a stored example; it is doing the work on whatever you hand it, which is the whole difference between a program and a recording.>>> len("supercalifragilisticexpialidocious")
34
len is short for length, and it counted the characters — thirty-four of them — in less time than it took you to find the closing bracket. Count them by hand if you like; we will wait. This is the first honest taste of what a computer is actually for. Not cleverness. Doing a boring thing perfectly, instantly, and the ten-thousandth time exactly as well as the first.>>> for i in range(1, 6):
... print("*" * i)
...
*
**
***
****
*****
range(1, 6) hands out the numbers 1, 2, 3, 4, 5 one at a time; for runs the indented line once for each of them; and "*" * i is win 2 again, building a longer run of stars every pass. Three details worth naming now. The ... is Python's way of saying go on, I am still listening — you do not type it. The four spaces before print are not decoration: indentation is how Python knows which lines belong inside the loop. And pressing Enter on the empty ... line is how you say "that is the whole loop" — then it runs. What you wrote is a program: work you described once and the machine did five times.>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
... and seventeen more lines like them
this is a small joke the language has been telling for decades. It prints the Zen of Python: nineteen one-line opinions about how code ought to be written, tucked into the language itself by the people who built it. Read the whole thing on your screen. You were not handed a box of tools — you were handed a box of tools with a letter inside from the people who made them, and that letter turns out to be a decent summary of everything this course is going to argue.NameError: name 'nmae' is not defined — and read that once more, because it is not a telling-off. It is the machine explaining itself: here is what I was doing, here is the line I was on, here is the exact word I could not make sense of. An error message is the most honest output a computer ever produces, and reading them fluently is not a side skill you pick up by accident — it is something this course teaches on purpose, chapter by chapter, until a traceback reads like a colleague pointing at the line. So go and experiment. Type something absurd deliberately, just to hear what it says back. And when you close the window, the entire session vanishes without a trace — which is section 3's lesson about RAM, felt in your hands before we ever name it.len(night) and for track in night work on something you wrote.pip install — not a script in a folder.01A computer is a box of switches
Let's start with the smallest real thing in the machine. Strip away the screen, the keyboard, and the glowing logo, and what's left is a box of switches — billions of them. Each one is a transistor: a speck of silicon that either lets electric current through or blocks it. Watch what it can do, and, more to the point, what it can't. There's no third setting, no "half-on," no "maybe." Off or on. That is the entire vocabulary. We give the two states names, 0 and 1. One switch's worth of that, one on-or-off, is a bit (short for binary digit). A bit is the smallest piece of information there is: one wire, carrying voltage or not. You cannot cut information any finer than that.
Here's the thing to sit with. Everything your machine "knows" is, all the way down, just a pattern of ons and offs. This sentence, a photo of your grandmother, your favourite song — nothing else is in there. That's what sets up the puzzle driving this whole chapter. Take one small, real fact from a playlist we'll carry through the entire book: the song "Blinding Lights" by The Weeknd runs 200 seconds. Now try to store that in switches. A single switch can say yes or no. It cannot say 200. So already, on the very first fact we care about, the atom of the machine looks far too small for the job. Hold onto that tension. Resolving it is the entire trick of digital computing.
The deeper cut — how one switch became two, and why "off or on" is a useful lie
Saying a transistor is simply "off or on" is a labelled simplification. It's true enough to build your whole mental model on, and we'll refine it exactly once, right here. In reality the voltage on that wire is a smooth, continuous quantity. It can sit anywhere between the ground rail and the supply rail. What makes it behave as a clean 0 or 1 is a deliberate design choice. The circuit treats everything below a low threshold as "0" and everything above a high threshold as "1." The ambiguous middle band is forbidden — a no-man's-land the signal is engineered to snap out of within nanoseconds. So the switch is analogue underneath, and the two-state behaviour is imposed on top of it. This is why the digital abstraction is so sturdy. Even if a real 1 arrives a bit droopy, it still reads as a perfect 1 as long as it lands above the threshold, so the droop never accumulates. That single trick — quantise, then round — is what lets a billion imperfect physical switches compute a flawless answer. Everywhere else in this book we'll happily say "off or on," now that you know what's really underneath.
So the atom is settled: one switch, two possibilities. The whole rest of the story is combination. Two switches side by side don't give you three possibilities — they give you four (off-off, off-on, on-off, on-on). Three switches give eight. Each switch you add doubles the count, and it's that doubling, not the switch itself, that will finally be big enough to hold a 200.
One switch stores exactly two possibilities. So how many switches does 200 need — and arranged how? →
02Counting with two fingers
You already know this trick — you just know it in base ten. Read 743 right to left: 3 ones, 4 tens, 7 hundreds. Each column is worth ten times the one to its right, and each digit only says one thing: how many of that column to take. And that's the whole machinery of a number: a row of columns with fixed values, and digits reporting how many of each.
Binary plays the identical game with two instead of ten. Now each column is worth double the one to its right: …8, 4, 2, 1. And because a binary digit can only be 0 or 1, it stops saying how many and just says in or out. That last part is the quiet miracle. In or out is the one and only thing a transistor can report: current flows, or it doesn't. Ten distinct voltage levels would be a nightmare to build reliably. Two is just "on the near side of a threshold" or "the far side." So binary isn't a weird dialect computers were forced into. It's the richest counting system you can run on a switch. The number 1011 therefore means 8 + 0 + 2 + 1 = 11. Same place-value idea you've used since grade school, just a smaller alphabet.
1011 column by columnfigure1011 is 8+0+2+1 = 11. No new math — just base 2, where a lit column contributes its value and a dark one contributes nothing.✗ The myth
"Computers think in ones and zeros" — some alien logic, fundamentally unlike how humans count.
✓ The reality
It's ordinary place value with two symbols instead of ten. The only reason it looks foreign is the small alphabet; the arithmetic underneath is the same doubling-columns idea you already trust in base ten.
Group 8 bits together and you get a byte — the standard parcel of computer memory, the box everything else is stacked out of. Why does one byte top out at 256 patterns? Count them. One switch makes 2 possibilities: off and on. Add a second switch and every old pattern splits in two, once for the new switch off and once for it on. So the count doubles each time you add a switch: 2, 4, 8, 16, 32, 64, 128… and at eight switches, 2⁸ = 256 distinct patterns. That's exactly enough to count from 0 to 255. (256 patterns, but the first one is zero, so the largest is 255 — an off-by-one worth remembering.)
And look — our playlist fits. "Blinding Lights" runs 200 s, and 200 = 128 + 64 + 8 = 11001000. One byte holds the running time of the song with room to spare. Its neighbour "Titanium" at 245 s is 11110101, still one byte. Now a quick honesty note, because the original made this promise and we keep it. The song itself, the actual audio, is millions of bytes. The thing that fits in a single byte is just one small number about the song: its length in seconds. But those millions of bytes have a shape. The audio itself is a waveform — a long run of samples, each one a plain number saying where the speaker cone sits at that instant. A song's duration is metadata a human typed once. Its samples are the metal the playlist rides on, and we'll follow this very waveform up the whole book. Drag the slider below and watch the eight switches spell out whatever number you point at. (Later, Python even lets you flip these bits by hand with operators like << — chapter 4.)
0b plus the eight bits.11001000 is only "200" because we decided to read it as a plain count. Feed the very same switches to a different agreement and the meaning shifts underneath you: the pattern 01000010 is the number 66, and 66 is the code the whole system agrees means the capital letter 'B' — Python will hand it straight back with chr(66). Read one byte as a colour channel instead and it's a dim shade of grey. The bits are only in/out; meaning is the interpretation we lay on top. Keep that in your pocket — it's why one file format can't open another's bytes.0b0101110111000000, sixteen of them. Same bits; "a sound sample" is only the reading we lay on top, just as 0b01000010 was either 66 or 'B'. But look at the size — 24000 > 255, so eight switches can't reach it. This one sample needs a 2-byte team: a signed 16-bit integer, range -32768 to 32767, with 0 = cone at rest. There's your second concrete reason multi-byte teams exist — not the song's length this time, but a single one of its millions of samples. Python packs a run of them as array('h', [0, 12000, 24000, 18000, -6000, -22000, -14000, 3000]) — the 'h' means two bytes each — and 24000 is the loudest sample in that run.You can watch Python itself agree with the figure. bin() turns a number into its switch pattern, and reading a binary literal turns it right back — no magic, just the same base-2 place value the slider showed you:
# a number is just how we read a pattern of switches
print(bin(200)) # 0b11001000 — "Blinding Lights", 200 s
print(bin(245)) # 0b11110101 — "Titanium", 245 s
print(0b11001000) # 200 — Python reads the switches back
print(8 + 0 + 2 + 1) # 11 — the 1011 from Fig 02
print(bin(24000)) # 0b101110111000000 — one sample: 15 bits, too big for a byte → 2-byte 'h'64 bytes from 216.239.38.120" — and look hard at that address: four numbers, every one landing between 0 and 255, because each dotted octet is exactly one byte (that's the whole reason an IP number never reads 256). A high-quality MP3 of "Blinding Lights" is roughly eight million bytes — and that's the compressed file; unpacked to its raw waveform the same song is about 8.8 million samples × 2 bytes ≈ 17 MB of switch patterns. Your phone's storage, that email attachment's size, the "500 MB left" warning — all of it is priced in this one unit. The byte is the atom everything else on a computer is measured in.The deeper cut — why 8, and where hex comes from
Eight bits per byte wasn't handed down by nature. It was a design choice that won. Early machines used 6-, 7-, and 9-bit groupings, but 8 hit a sweet spot. It gave enough patterns (256) to cover every letter, digit, and punctuation mark of early text with room to spare, and it was a clean power of two that hardware likes.
Writing bytes as eight 0s and 1s is exhausting to read. So programmers group the byte into two halves of four bits and give each half a single symbol: 0–9, then a–f for the values ten through fifteen. That's hexadecimal. One byte becomes two tidy characters. 11001000 is c8, and Python writes it 0xc8. It's the same byte and the same 200, just a denser handwriting. You'll see hex everywhere colours are written, like #5ede78 (that green is three bytes: red, green, blue).
A byte maxes out at 255, and a playlist is thousands of bytes of titles and lengths. Where do they all live, and how does the machine find the right one? →
03RAM: a street of numbered boxes
RAM (random-access memory) is where a running program keeps the data it's working on right now. The honest picture is almost embarrassingly simple: one enormous row of byte-sized boxes, each stamped with a serial number called its address. Box 0, box 1, box 2… on an 8 GB machine, about 8.6 billion boxes in a single unbroken line. The random-access part is the magic word: you can open box 5,000,000 exactly as fast as box 5. You don't walk the street counting doors. You're handed the house number and you're there. One jump, same cost, anywhere in the line.
Store our song and the layout becomes concrete. The title "Blinding Lights" is fifteen characters — fourteen letters plus the space between the two words — so it claims fifteen boxes, one per character. Even the space is a byte: it holds the number 32. And the box for the first letter B doesn't hold the shape of the letter. It holds the number 66, a shared letter-code the whole system agrees on (first approximation for now, and chapter 2 refines exactly which code and why). The length 200 fits in one more box, since it's under 255. Values too big for a single byte — anything past 255 — get a fixed team of boxes, typically 4 or 8 wired to act as one wider box. Store a whole row of song lengths in 4-byte teams, and their addresses tick past like house numbers down one side of a street: 6000, 6004, 6008…, always four apart. That even stride is the whole trick. Asking "give me length #3" needs no searching at all. The machine computes start + 3 × 4 = 6012 and jumps straight to that box. Arithmetic, not hunting.
Be precise about what this picture is, though. This is the raw-metal layout, the way C and NumPy pack a row of numbers wall-to-wall. Python's own list reuses the even-stride trick, but with a twist. Its evenly spaced slots don't hold the lengths themselves. They hold the 8-byte addresses of where each length-object actually lives elsewhere in RAM. Same street, one extra hop — and chapter 6 shows that layout in full. One more thing your program never does: it doesn't pick its own boxes. The operating system hands each program its share and keeps the ledger.
6000 + i × 4. That's the difference between an array (instant) and rummaging (slow).It doesn't. A box holds a number, full stop — no label, no unit, no type painted on the side. The meaning lives entirely in the program that reads it. The same 200 is a letter-code, a song length, or a shade of grey, depending only on what the code decides to do with it. Hold that thought. It grows up into Python's type system in chapter 2, which is really just the discipline of remembering what each box was meant to be.
start + i × stride is a single fast multiply. Pick an odd team size and every lookup would need awkward arithmetic — the hardware would rather waste a byte of padding than lose the even stride.The deeper cut
Real tools write addresses in hexadecimal — base 16, digits 0–9 then A–F. The reason is exact, not cosmetic. One hex digit is precisely 4 bits, so one byte is always two hex digits. That's why 255 = 0xFF and our address 5000 = 0x1388. Hex lets you read the raw bits at a glance, where decimal blurs them.
And here's a second layer you'll eventually trip over: the addresses your program sees are virtual, not the real chip locations. Each program gets a private, pretend address space that starts fresh. The MMU (memory-management unit, hardware sitting right beside the CPU) then translates virtual to physical on every single access. That's how two programs can both believe they own address 5000 without a fistfight. Their "5000"s quietly map to different physical boxes.
Billions of boxes, patiently holding numbers. But boxes don't add, compare, or copy themselves. Who does the actual work? →
list adds one more hop, its slots holding addresses instead — §3). Laid out inline like this, reading item i never searches: the machine computes base + i × stride in one shot and jumps straight to that box. Press Next and watch the address get built.04The CPU: the only part that does anything
Everything else in the machine just holds or moves data. The CPU — the central processing unit — is the one part that actually does anything with it. It is the machine's single busy worker, and its entire life is one loop: the fetch–decode–execute cycle. Fetch the next instruction from RAM. Decode what it means. Execute it. Then round again, forever, until you pull the power. Each instruction is almost insultingly small: copy one box into the CPU, add two values, compare two values, write a box back out. There is no step where the hardware "understands" your program. There is only this loop, spinning. (One labelled simplification, since this book flags them. A real core doesn't finish one fetch–decode–execute before starting the next. It keeps many in flight at once, pipelined, and issues several per tick, superscalar. One-instruction-at-a-time is the right logical model to reason with. Just don't read the "four instructions ≈ one nanosecond" figure below as four strictly serial ticks.)
Here's the hard constraint that forces the next piece of hardware to exist. The ALU cannot reach into the RAM street and add two boxes where they lie. It can only operate on values already held inside the CPU. So before it can add anything at all, the operands must first be copied in. For exactly that, the CPU keeps a handful of private scratch cells of its own, called registers — the only place arithmetic is allowed to happen. They're also far faster than RAM, for the plainest possible reason. They're etched into the CPU chip itself, so a value in a register never has to travel down the wires from the RAM street at all. It is already home. The adding and comparing happens in a dedicated circuit called the ALU (arithmetic-logic unit), which reads from those registers and writes back to them. And a quartz clock paces the whole loop at roughly 4 GHz: four billion cycles every second, one tick every quarter of a nanosecond.
Watch it total our playlist — and watch a single byte visibly run out. "Blinding Lights" (200 s) fits in one byte, and "Titanium" by David Guetta (245 s) fits in one byte too. But their sum 200 + 245 = 445 sails past 255, too big for any one byte to hold. That overflow is precisely why the machine parks each length in a 4-byte team (§3) rather than a lone byte. The wider box leaves the headroom the answer needs. The two lengths sit in RAM. The CPU loads both into registers, the ALU adds them, and it stores 445 back to a third slot. Four instructions. Step through it below.
The deeper cut: where does the CPU keep its place?
One register is special: the program counter (PC). It holds the RAM address of the next instruction to fetch. The "fetch" step reads whatever the PC points at; then the PC advances to the following instruction, and the loop repeats. That's the whole trick behind a program having an order.
It's also how a program can make a decision. A "compare" instruction can set the PC to jump somewhere else instead of just stepping forward: "if A is bigger than B, continue from address 7200." Loops and if-statements, all the way up to the branch in your Python code, bottom out as the CPU writing a new number into the program counter. There is no magic control flow underneath. There is only "which box do I read next."
Notice, though, exactly what the CPU obeyed: terse binary instructions like LOAD, ADD, and STORE. They're encoded as numbers, and specific to this particular chip family. The CPU has never seen a letter of English. Yet you write English-ish text in a .py file: 200 + 245. Someone has to bridge that gap.
You write English-ish text; the chip speaks numbered instructions. Someone has to translate — and that someone is the next piece of the machine →
05The ladder from your text to the metal
A CPU understands exactly one language: its own machine code — raw binary instructions, a different dialect for each chip family. Your laptop's x86 chip and your phone's ARM chip both do arithmetic. But the exact byte patterns that mean "add these two numbers" are not the same on both. Hand one chip the other's machine code and it's gibberish. What you write instead is source code: human-readable text, the same on every machine. Between the two sits a translation step, and there are two classic strategies for it. A compiler translates the whole program to machine code once, ahead of time, and hands you a finished executable. That's fast to run, because the translating is already done (C, C++, Rust). An interpreter is itself a running program that reads your code and performs it on the spot, translating each piece the moment it's needed. In its pure form nothing translated is kept, so it re-does that work every run. That's slower per run, but it starts instantly and needs no build step. (Real Python is subtler: it quietly does save its half-translated work, as you'll see in a moment.) Picture a novel translated once and printed forever, the compiler. Now picture a live interpreter at a summit, re-translating the same speech from scratch every single time it's delivered. That re-translation on every run is exactly where interpreted programs spend their extra time. (A JIT is the hybrid that refuses to choose. It starts out interpreting, watches which parts run over and over — the "hot" loops — and quietly compiles just those to machine code mid-run. That's the trick inside the JVM and V8, the engine under your browser's JavaScript.)
✗ The myth
You install Python and your CPU "learns Python." From then on the chip runs your.py file directly.✓ The reality
The CPU only ever runs machine code — it never changes.python.exe is a pre-built machine-code program; your .py file is just data — text — that this already-running program opens, reads, and acts out. Deleting Python doesn't un-teach the chip anything; it just removes the actor.And here's Python's actual pipeline, which is subtler than the one word "interpreted" suggests, because it quietly does both jobs. When you run a file, CPython first compiles your text into bytecode: compact numbered instructions for a made-up, portable CPU that doesn't physically exist. This is an intermediate representation — halfway down the ladder, no longer your text but not yet your hardware's machine code either. Then CPython's virtual machine — a program written in C that behaves like that made-up CPU — walks the bytecode and interprets it, one instruction at a time. So there are two translation rungs stacked on top of each other, and neither one is optional. Two words are about to do real work in the disassembly just below, so pin them now. A function is a named mini-program. You call it to set it running, and when it finishes it returns a value to whoever called it, the caller (you'll build your own in chapter 9). And that made-up CPU has a definite shape: it's a stack machine. Instead of §4's named registers it keeps one scratch pile called a stack that you may touch only at the top — push a value on, pop one off, last-in, first-out. Why a pile rather than registers? Because a single uniform push/pop pair can evaluate an expression nested to any depth without inventing a name for every intermediate value. It's the simplest engine that will run any code at all.
dis.dis and read the rungs. Every line it prints is one tick of the virtual machine: the exact instructions the VM will walk, in order, when that function runs.import dis
def total_seconds(levels, titanium):
return levels + titanium
dis.dis(total_seconds)
# prints the function as VM instructions:
# LOAD_FAST levels ← push Blinding Lights (200) onto the stack
# LOAD_FAST titanium ← push Titanium (245) on top of it
# BINARY_OP + ← pop both, push their sum (445)
# RETURN_VALUE ← hand 445 back to the callerFour instructions, and you've just watched the whole ladder in miniature. Your one line return levels + titanium became a short list of moves for a stack, and the VM performs them left to right. Run total_seconds(200, 245) and those four ticks produce 445 — the full playlist, Blinding Lights plus Titanium, in seconds.
The deeper cut
__pycache__ folder that appears next to your files, holding names like playlist.cpython-312.pyc. The number is the CPython version that produced it: bytecode is portable across operating systems (the .pyc from your Mac would run on a Linux server) but not across Python versions — a 3.12 .pyc means nothing to 3.13, so the version is stamped right into the filename. Machine code, for contrast, is portable across neither OS nor CPU family; bytecode's portability is exactly the payoff for adding that extra rung. And the "made-up CPU" has a specific shape: it's a stack machine. It keeps a small scratch stack of values;
LOAD_* instructions push values onto it, and operations pop their inputs off the top and push the result back — which is precisely the LOAD / LOAD / add / return dance you just disassembled. Hold onto that push-and-pop picture: the very same last-in-first-out discipline is about to reappear one level up, in how Python keeps track of function calls.So a software CPU is executing your program. While it runs, it has to park thousands of values somewhere in those RAM boxes from the last section. It turns out the parking lot isn't one uniform space — it has two very different districts, with very different rules →
06Two districts of memory: stack and heap
The RAM your program is handed doesn't stay one undivided slab. But before we name the two pieces it splits into, feel the problem that forces the split. A running function needs a scrap of private space: its arguments, its local names, and one thing you'd never think to ask for — a note of where to jump back to when it finishes. Now let one function call another, which calls a third. Each call needs its own locals and its own return-note. And here is the constraint that decides everything: nested calls finish in the exact reverse of the order they started, so the last one called is the first to hand control back. That isn't a style choice. The nesting forces it. So this bookkeeping is born and dies in strict last-in, first-out order.
But a second fact cuts clean across the first: some values must outlive the call that made them. A function that builds a list and hands it back cannot let that list die when its own locals are swept away, because the caller is still holding it. So your program carries two kinds of lifetime that refuse to line up. There's bookkeeping that vanishes in perfect LIFO lockstep, and there are objects that live exactly as long as something still points at them, reached at any time by anyone. One undivided slab cannot serve both cleanly. So the machine splits its memory into two districts, and each one is arranged to fit the single lifetime it has to hold. Learn to feel which district you're standing in, and the whole language stops being mysterious.
The stack holds that bookkeeping. And because the bookkeeping is strictly LIFO, it can be stored the cheapest way there is: one contiguous run of this chapter's numbered boxes with a single marker called the top. First, one word we've been leaning on. A function is a named mini-program living inside your program, something like play_song. You call it to set it running, and it returns to whoever called it once it's done (you'll write your own in chapter 9). Every call that is currently running gets a frame: a block holding its local names and its return address. A call pushes its frame by nudging the top up by the frame's size. A return pops it by nudging the top back down — last opened, first closed, the discipline computer people call LIFO. And notice the reward for that rigid shape: there is no searching and no free-space ledger. Allocating is "add to a pointer," and freeing is "subtract from a pointer." That is the entire reason the stack is fast, and the reason it's called a stack: it only ever grows and shrinks at the one end, like spring-loaded plates. It cleans up after itself for free — when a call returns, the top simply moves down and its frame is gone.
The heap is arranged for the opposite lifetime. Objects don't obey LIFO, since a list made early can easily outlive a list made late. So the heap is an open warehouse floor, where a block of any size can be claimed at any address and released at any time, in any order. The number 200, the text "Blinding Lights", and later every playlist and object you'll ever build, all live out here. That freedom carries a price the stack never pays: the allocator must track which regions are free and hunt for one big enough on every request. It also forces the one extra hop we'll lean on for the whole course. A name sitting in a stack frame doesn't hold the object. It holds the address of an object over on the heap. And cleanup out here isn't vague background magic. Every heap object carries a reference count, a tally of how many names point at it, and the instant that tally hits zero the object is freed on the spot. (A separate collector runs only to catch cycles — objects that point at each other and prop each other's count up with no arrow reaching in from outside.)
One honesty note, because this course refuses to sell you a tidy lie. In CPython (the standard Python you'll install next section) the frames are themselves small objects that also sit in heap memory. So "stack" isn't a fenced-off street somewhere across town. It names the strict last-in-first-out discipline those frames obey, not a separate slab of hardware. The roles in the picture below are exactly as drawn. Only the literal geography is a simplification, and now you know precisely where.
Reading a static drawing is one thing, and watching frames breathe is another. Drag the step below and walk through a tiny run: main calls play_song("Blinding Lights"), that call returns, and finally the collector sweeps. Notice how the two districts move on completely different clocks. The stack snaps back the instant a call returns, while the heap objects linger until nothing points at them.
main is running. Its frame sits alone on the stack; the heap is still empty.It does, and it's not shy about it: a small integer object costs 28 bytes in CPython. You can check it yourself with sys.getsizeof(200), and chapter 2 will open one up and show you exactly what those bytes are doing. That's roughly twenty-eight times what the raw value needs. Why pay it? Because a Python integer isn't just the value 200. It carries its type, a reference count, and enough machinery to grow past a byte, past a billion, past anything your hardware natively counts, without you ever thinking about it. That trade — space for flexibility — is the running story of this entire course, and now you've met it face to face.
The deeper cut: why the stack can afford to be so fast
Pushing and popping a frame is about the cheapest thing a computer does. Because the stack only ever changes at the top, allocating a frame is little more than moving a single pointer down, and freeing it is moving that same pointer back up. There's no searching, no bookkeeping ledger, and no collector required. That's why local names are quick and self-cleaning.
The heap pays for its freedom with exactly the work the stack skips. Objects can be any size and die in any order, so the heap leaves holes when things are freed, and something has to track what's live versus garbage. CPython's main trick is reference counting: every object keeps a tally of how many names point at it, and the instant that tally hits zero the object is freed on the spot. A separate cycle-detecting collector cleans up the rarer case, where two objects point at each other but nothing outside points at either. You'll meet reference counts for real in chapter 3. The interactive above is their gentlest possible preview.
Enough theory — time to make the ladder fire for real. Next: install Python, type one line, and watch print("Blinding Lights") travel from your keyboard, down through every rung, to the bare metal and back →
Every program here is a real Python 3 program about the same thing a computer is made of — bits. We start by writing numbers in other bases, wire up the four logic gates, slide bits sideways, then handle raw bytes and the characters they encode. Press Next and watch the bits move.
01Type everything. Reading code is watching somebody else swim — it all looks obvious right up to the moment you are the one in the water. The lines here are short on purpose, so typing them costs you seconds and buys you something reading never does. 02Predict before you press. Many pages hand you a step-through widget with a Next button, and it will keep stopping to ask what happens next before it will show you — that is deliberate. Say your answer out loud, or under your breath, then press. The guess is what makes the answer stick; pressing Next without one turns a machine you could have understood into a slideshow you watched. 03Wrong guesses are the mechanism working. When a prediction misses, you have just located a belief you did not know you were holding, and that is the only way anyone has ever fixed one. The misconception boxes scattered through these chapters are not there for the slow reader. They are there because nearly everybody arrives holding exactly those beliefs, and we would rather name them out loud than let them quietly cost you a fortnight.