◈ python mapVol 1 · Ch 00/14
Volume 1 Python, from the metal up · chapter 00

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.

iolinked · chapter 00 — the checkpoints6 steps
$ sections covered in From the metal up
01A computer is a box of switches
02Counting with two fingers
03RAM: a street of numbered boxes
04The CPU: the only part that does anything
05The ladder from your text to the metal
06Two districts of memory: stack and heap
start here before the theory · ten minutes · five wins
Your first ten minutes

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:

Windows
Press the Windows key, type terminal, press Enter. In the window that opens, type py and press Enter.
macOS & Linux
Press Command+Space, type 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.

If installing turns into a wall today
It happens, and it is not a character flaw — a locked-down work laptop, a borrowed machine, a download that will not finish. Go to python.org and click Launch Interactive Shell: a real Python runs inside the browser tab and hands you the same >>>. 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.
win 1 · type thisyou outrun a pocket calculator on line two
>>> 2 + 2
4
>>> 2 ** 100
1267650600228229401496703205376
The first line is the machine being polite. The second is the machine showing its hand: ** 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.
win 2 · type thistext obeys arithmetic
>>> "your name " * 3
'your name your name your name '
You just multiplied text. Nobody told Python what "times three" ought to mean for a run of letters, and yet * 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.
win 3 · type thisthe machine counts so you do not have to
>>> 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.
win 4 · type thisyour first program that does something repetitive
>>> for i in range(1, 6):
...     print("*" * i)
...
*
**
***
****
*****
Two lines you typed, and a shape appeared. 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.
win 5 · type thisthe language shipped with a philosophy
>>> 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
import is how you reach for something that already ships inside Python, and 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.
Nothing you type at that prompt can break the computer
This is worth saying plainly, because it quietly stops a lot of people before they start. A typo cannot hurt anything. Python only reaches your files, your settings or the network when a program deliberately asks it to, using tools you will not meet until Chapter 15 — so nothing you type by accident at this prompt can touch your photos, and there is no key you are not allowed to press. The worst outcome is that Python stops and prints a few lines ending in something like 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.
the arc real programs · real chapter numbers
What you will build
ch 05
A game you can lose to. Rock, paper, scissors against the machine — it picks, you pick, somebody wins, and the loop comes round again until you quit. Your first program with an opinion.
ch 12
A playlist that behaves like an object. Not a bare list of songs any more, but a thing with behaviour of its own, so that len(night) and for track in night work on something you wrote.
ch 13
The same playlist, hardened. Bad input stops being a crash and becomes a refusal you designed: your own error types, saying no on purpose, with a reason attached.
ch 14
A jukebox with its own manual. Volume 1's capstone — every idea of the volume in one program you can read top to bottom: a menu loop, custom errors, and docstrings so the code answers questions about itself.
ch 15–16
It learns to remember. Files, opened the honest way down to the system call, then made durable — so what you saved is still there after the machine restarts, and you know exactly why.
ch 30
A real app you could hand to a friend. A media library with a proper command-line front end, tests that prove it works, and a package someone else can pip install — not a script in a folder.
vol 3–4
The toolkit under every app on your phone. What "fast" really means, halving and searching and sorting, then the structures themselves — lists, hash tables, trees, graphs, heaps — and how to pick the right one on sight.
Every one of those is assembled from parts you will understand down to the metal. Nothing gets pasted in from somewhere else, and nothing is taken on faith — wherever a thing looks like magic, we stop and open it. That is the deal this course makes with you, and the next six sections are where we start keeping it.

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 ONE IDEA TO CARRY FORWARD
A bit has exactly two states, and a computer has only bits. Every number, letter, colour and sound you will ever see a computer handle is built by lining up more of these two-state switches. There is no other ingredient. The rest of this chapter is just watching that one idea grow.
One transistor, two statesfigure
switch open — current blocked bit = 0 switch closed — current flows bit = 1
Fig 01 — One transistor, two states: current blocked reads as 0, current flowing reads as 1. That is a bit — the atom of all data. Everything above this page is built from copies of exactly this.
Wait — why two states and not ten, so a switch could just hold a digit 0–9 directly? Engineers tried. The trouble is telling the levels apart reliably: with two states you only have to distinguish "clearly off" from "clearly on," and a little electrical noise never flips one into the other. Ten voltage levels crammed onto one wire would smear into each other the moment the chip warmed up. Two states is not a limitation the universe forced on us — it's the most robust choice, and robustness at billions of switches is everything.
↺ reframe
Don't picture a computer "understanding" 200. Picture a row of light switches on a wall. Each is just up or down. The machine never sees the number — it only ever sees which switches are up. Any meaning is a pattern we agreed on beforehand, not something the switches themselves contain.
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.

Wait — why "two fingers"? Because if all you can do is raise or lower one finger, you can still count as high as you like by lining up more fingers and giving each one double the weight of the last. Ten fingers held this way count past a thousand. A byte is eight such fingers.
Reading 1011 column by columnfigure
2⁰ 8 4 2 1 1 0 1 1 8 0 2 1 + + + 8 + 0 + 2 + 1 = 11
Fig 02 — Binary is place value with doubling columns: 1011 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.)

One byte — flip 8 switches, make any number 0–255▸ drag me
128 64 32 16 8 4 2 1 1 1 0 0 1 0 0 0 128 + 64 + 8 = 200 0b11001000
200
Each switch is worth double its right-hand neighbour — 8 of them cover every number from 0 to 255. Find 200 (the length of "Blinding Lights"), then 245 ("Titanium"), then 255 (all switches on), then 1 (just the last one).
Fig 02b — The lit (green) switches always sum to the number on the slider — that sum is the number, read in base 2. The cyan line is how Python would print it: 0b plus the eight bits.
↺ A byte isn't a number — it's a shape
The same eight-switch pattern 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.
One sample is two bytes — the waveform's turn
Take a single instant of "Blinding Lights". The speaker-cone position at that instant is one number — say 24000 — and, exactly like the byte above, that number is a row of switches: 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:

bits.pypython
# 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'
You've been reading bytes for years
Ping a server and it answers "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 af 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? →

Trace3 step the machine — idea · code · memory move together
Building a byte — how 200 becomes 11001000
Read the number one column at a time, biggest place-value first. Take the column if it fits in what's left. Press Next and watch the switches, the code and the memory move as one.
8 columnsbase 20–255
The idea — eight switches, biggest firstbyte · 8 bitsinit
In plain words
Under the hood
SETALU · the operation
Program · to_byte.py
variables · type
Memory · registers + object
registers — the running state
bits str · the byte so far
what's happening
beat 1 / 1

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.

"Blinding Lights" laid out in RAM, one byte per boxfigure
RAM — one long row of byte-sized boxes, each stamped with an address B 5000 l i n d i n g 32 5008 L i g h t s 5014 200 len 5015 one byte per character — even the space is a byte (code 32) · the length 200 rides in box 5015 · 8 GB ≈ 8.59 billion boxes
Fig 03 — "Blinding Lights" spelled across fifteen boxes (5000–5014) — one byte per character, the space included — with the length 200 in box 5015. Data has no home without an address.
↺ the address is the speed
"Random access" isn't about randomness — it's that every box is one hop away, so the machine can compute where a thing lives instead of scanning to find it. That single property is why an array lookup is instant and why "the 3rd item" costs the same as "the 3-millionth."
Drag the index — watch it compute the address, not search for it▸ drag me
a row of song lengths, each a 4-byte team · base address 6000, stride 4 #0 200 6000 #1 245 6004 #2 222 6008 #3 213 6012 #4 190 6016 #5 258 6020 #6 201 6024 #7 176 6028 6000 + 3×4 = 6012 → holds 213 s no scanning · the address is a one-line calculation
#3 → box 6012
Slide the index. The highlighted box is never found — its address falls straight out of 6000 + i × 4. That's the difference between an array (instant) and rummaging (slow).
Fig 03b — Even stride turns "the i-th item" into a multiplication. This is the beating heart of every array, list, and NumPy row you'll ever touch.
RAM FORGETS
RAM is volatile: cut the power and every box blanks in an instant. That's why unsaved work dies with a crash — it never left RAM. Your SSD is the opposite: persistent but far slower, so "loading" a file just means copying its bytes from SSD boxes into RAM boxes, and "saving" copies them back. The song only plays from RAM; it only survives on the SSD.
Wait — box 5002 holds 105 (the letter "i") and box 5015 holds 200 (a length in seconds). Both are just numbers sitting in identical boxes. How does the machine know which is a letter and which is a duration?

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.

WHY 4 OR 8, NEVER 3
The teams come in tidy powers of two (1, 2, 4, 8 bytes) so the stride stays a clean multiple and 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? →

Trace3 step the machine — idea · code · memory move together
Random access is arithmetic — how row[3] becomes address 6012
This is the raw-array model — the way C and NumPy pack numbers wall-to-wall, each value sitting inline in its own slot (a Python 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.
8 slotsstride 4O(1)
The idea — one contiguous block in RAMlist · 8 × 4Binit
addr = base + i × stride
In plain words
Under the hood
SETALU · the operation
Program · address_of.py
variables · type
Memory · registers + object
registers — the running state
row list · 8 ints, 4 bytes each — one contiguous block
what's happening
beat 1 / 1

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.

WHY REGISTERS BEAT RAM
It's pure distance. RAM is a separate chip; every read crosses a bus (the shared wires between CPU and memory). A register is on the CPU die, microns from the ALU. Same idea as keeping the two numbers you're adding in your hand versus walking to a filing cabinet for each one. Registers are tiny — a modern CPU has only a few dozen — so they hold just the values in active use this instant.

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 one loop all hardware runsfigure
FETCH read next instruction from RAM DECODE work out what it means EXECUTE do it — add, copy, compare 4 GHz ≈ 4,000,000,000 cycles / second one cycle ≈ a quarter of a nanosecond
Fig 04 — One dumb loop, repeated four billion times a second, is all the "intelligence" the hardware has. Every clever thing a computer does is this cycle, run enough times.
Step the CPU — add two song lengths▸ drag me
next instruction: — (drag the slider) CPU A · B · ALU — the adder circuit bus RAM 6000 200 Blinding Lights 6004 245 Titanium 6008 · total Two song lengths sit in RAM. The registers are empty scratch cells.
step 0 / 4
Every program you will ever run — including Python itself — reduces to steps this small. The green outline shows which box each instruction touches.
Fig 05 — Load, load, add, store. Four instructions turn two boxes in RAM into a third. That's a whole computation.
Wait — why does adding need four instructions when it's obviously "one" operation? Because the CPU can only add numbers that are already sitting in registers. It cannot reach into RAM and add in place. So two of the four steps are pure logistics — fetch the operands home — one is the actual add, and one ships the answer back out. Most of what a CPU does, by count, is moving values around so the small amount of real arithmetic has something to chew on.
FEEL THE SPEED
Those four instructions take the CPU about one nanosecond. In the time it took you to read this sentence — call it three seconds — a 4 GHz core could run on the order of twelve billion cycles. It could have totalled a playlist of a few billion songs. The reason your computer ever feels slow is never the adding; it's waiting — for the disk, the network, or a value stranded out in RAM.
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 →

Trace3 step the machine — idea · code · memory move together
The CPU only moves — adding 200 + 245 takes four steps
A CPU can't add two numbers while they sit in RAM. It must LOAD each into a register, ADD them in the ALU, then STORE the result back. Three of the four instructions just move values around — only one actually adds. Press Next.
4 instructions2 registers1 ALU
The machine — CPU · bus · RAM4 instructionsboot
CPU
A
B
ALUidle
bus
RAM
6000Blinding Lights200
6004Titanium245
6008total?
In plain words
Under the hood
IDLEALU · the operation
Program · add.asm
registers · type
Memory · registers + RAM
registers — the running state
RAM main memory · three cells
what's happening
beat 1 / 1

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.

The translation ladderfigure
SOURCE CODE — human-readable text (.py) song_seconds = 200 CPython compiles it — whole file, automatically BYTECODE — portable intermediate code LOAD_CONST 200 · STORE_NAME song_seconds the VM interprets it — one instruction at a time CPYTHON VIRTUAL MACHINE — a CPU made of software itself already machine code (compiled from C, long ago) which the real CPU executes HARDWARE CPU — machine code only, architecture-specific (x86, ARM)
Fig 05 — Python's real pipeline: compile to bytecode, then a software CPU interprets it. Two rungs, not one — the interpreter you keep hearing about is only the bottom half.
↺ The thing people get backwards
Most people think "compiled vs interpreted" is a property of the language — that Python simply is an interpreted language, the way a lemon is sour. It's really a property of the implementation, and CPython does both: it compiles your file to bytecode, then interprets that bytecode. (Other implementations choose differently — PyPy JIT-compiles the hot parts, and the same Python source could in principle be fully compiled ahead of time.) So "Is Python compiled or interpreted?" has a genuinely correct one-word answer: yes.
You can see the bytecode yourself
Python ships a disassembler in the standard library — nothing to install. Once chapter 1 has you running files, feed a function to 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.
disassemble.pypython
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 caller

Four 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.

One line of Python — count what the metal actually does▸ drag me
you write one line — return levels + titanium. how much does the metal actually do to run it? transistor switch-flips AT THIS RUNG, ONE LINE IS ~1 billion AND ONE OF THOSE IS a transistor flipping in/out ≈ the box of switches from §1, live AMPLIFICATION — YOUR ONE LINE, EXPLODED ×1,000,000,000 times the work of the line you typed. The cliff: ~1,000 instructions → ~a billion transistor flips — §1's switches, live.
the metal
Drag from your source line at the top down to the transistors at the bottom. The counts below the bytecode are order-of-magnitude estimates (hardware-dependent) — but the shape is real: one friendly line you typed becomes ~a billion in/out switch-flips at the metal. That is the price of every rung of comfort the ladder buys you.
Fig 05b — The same four bytecode ticks you just disassembled, followed the rest of the way down. Each rung the machine descends multiplies the work — and the bottom rung is nothing but §1's box of switches, flipping a billion times to keep your one-line promise.
The deeper cut
CPython caches compiled bytecode on disk so it doesn't have to recompile unchanged code every single run — that's the __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 →

Trace3 step the machine — idea · code · memory move together
How a + b runs — four bytecode ticks on a value stack
A stack is a scratch pile you may touch only at the top — push a value on, pop one off, last-in-first-out. CPython compiles return a + b into bytecode for a made-up machine built around exactly one such pile: LOAD pushes a value, an operation pops its inputs and pushes the result. With a = 200 and b = 245, the four ticks leave 445 sitting on top — the very total the CPU trace computed a level down. Press Next and watch the stack grow and shrink as one.
stack machineCPython 3.12a=200 · b=245
The value stack — grows up as values pushlist · TOS on topRESUME
▲ pushes grow the stack upward · bottom below
In plain words
Under the hood
BOOTALU · stack effect
Bytecode · total_seconds() compiled from: return a + b
locals · type
Memory · value stack (the CPU's scratch)
registers — the VM's running state
stack list · bottom → top
what's happening
beat 1 / 1

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.)

THE ONE SENTENCE TO MEMORISE
Plant this deep — it is the single most useful sentence in the course: in Python, your values live on the heap, and your names just point at them. A name is never a box holding a value; it's an arrow aimed at an object out on the heap. Chapter 3 will prove it to you with a one-line experiment you can run yourself.

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.

your program's RAM, split by jobfigure
your program's RAM frame: main program frame: play_song() grows ↓ on each call, shrinks on return 200 "Blinding Lights" "Titanium" STACK — call bookkeeping one frame per running function last in, first out; cleans itself tiny, rigid, predictable HEAP — where objects live any size, any order, any lifetime freed when its reference count hits 0
Fig 06 — Stack for who is doing what, heap for the things themselves. Every chapter ahead lives somewhere in this picture — keep it in your mind's eye.

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.

call stack vs. heap, step by step▸ drag me
0 · program starts
STACK — running now
HEAP — the objects
Step 0: only main is running. Its frame sits alone on the stack; the heap is still empty.
Fig 06b — The stack empties itself the moment a call returns; a heap object holds on only until its reference count — the running tally of names pointing at it — drops to zero, at which instant it's freed on the spot.
Wait — if the heap stores the number 200 as a full-blown object, how big is that object? A single byte held 200 just fine back in the counting section (a byte tops out at 255) — does Python really spend more than that on such a small number?

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 →

Trace3 step the machine — idea · code · memory move together
Two memory districts — the stack that snaps back, the heap that lingers
A running program splits its RAM in two: the stack keeps one frame per running call (last in, first out, self‑cleaning); the heap keeps the actual objects (any size, freed later by the collector). Press Next and watch a call push a frame, make objects, then return and let them die.
stack · LIFOheap · GC2 objects · 84 B
The two districts — stack (calls) · heap (objects)RAMrun
Stackcall frames · LIFO
main()
no locals
play_song(title)
title, length
Heapobjects · freed by GC
str'Blinding Lights'no refs56 B
int200no refs28 B
In plain words
Under the hood
RUNALU · the operation
Program · memory.py
in scope · type
Memory · stack + heap
registers — the running state
heap · objects what's allocated right now
what's happening — the two clocks
Stack
Heap
beat 1 / 1
PROGRAMS run them line by line — the same ideas, written the many ways Python offers
Chapter 00, in working code

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.

Counting past ten — bases & literals
The same integer can be written in binary, hex, or octal. Python's literals and inspector functions just change the costume, never the number.
The four logic gates — & | ^ ~
Bitwise operators work on every bit in parallel. AND masks, OR sets, XOR toggles, NOT flips — the whole of digital logic in four symbols.
Sliding bits — shifts & masks
Shifting moves every bit left or right, doubling or halving. Combine shifts with an AND mask and you can pack many fields into one number and pull them back out.
Bytes — the raw material
A bytes object is a sequence of numbers 0–255. Converting ints to and from bytes is how numbers cross wires and files — and why a byte that overflows quietly wraps.
Characters are just numbers
Text is bytes wearing a lookup table. ord gives a character's code point, chr turns a number back into a character — and once you see that, letters do arithmetic.
How to read this course

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.

end of chapter 00 · six sections
iolinked
designed & developed by Ajai Raj
Python, from the metal up · a product of iolinked