How a Computer Runs Code
By the end of this lesson you will be able to explain — in real pictures, not magic — what a computer actually is, how numbers are the only thing it ever stores, how the CPU executes one instruction at a time, how your Dart source code turns into something the machine can run, and where your variables and objects actually live in memory (the stack vs the heap).
1. What is a computer?
Every computer — a phone, a laptop, a smartwatch, a Kaggle GPU box — is built from the same idea:
Input → Processing → Output. Data comes in, the machine transforms it by following instructions, and a result comes out.
There are two halves to every computer, and they do very different jobs:
- Hardware — the physical stuff you can touch: the CPU chip, the RAM sticks, the disk, the screen. Hardware on its own can do nothing; it is just silicon and wires.
- Software — the instructions (programs) that tell the hardware what to do, step by step. Software cannot run on its own either — it needs hardware to execute it. A Dart program, an operating system, a game — all software.
The same three words as three lines of Dart (run it: the output is shown in the comment):
2. Bits, bytes & binary counting
One switch (one bit) can only tell you two things. But if you line up several switches side by side, and give each position a place value that doubles each time (1, 2, 4, 8, 16 …) — exactly like decimal place values are 1, 10, 100, 1000 — you can count much higher. Adding up the place values of every switch that is ON gives you the number.
Watch how a 4-switch (4-bit) counter behaves exactly like an odometer: when a digit can't go any higher, it rolls back to 0 and "carries" into the next digit. Binary only has two digits (0 and 1) instead of decimal's ten, so it carries much more often — but the logic is identical to how 9 + 1 = 10 on paper.
In Dart a bit is a 0 or 1 inside an int, the place value of bit p is 1 << p (2 to the power p), and the number is the sum of the place values of the ON switches:
Input size → what's feasible: n bits give 2n patterns — 4 bits = 16, 8 bits = 256, 64 bits (Dart's native int) = 264 ≈ 1.8·1019. Writing a number in binary needs about log2(n) steps, so even the largest 64-bit number is only 64 steps.
A group of 8 bits is called a byte. With 8 switches you get 2⁸ = 256 possible patterns (0 through 255). Try any byte value yourself below — the algorithm shown (testing each place value from largest to smallest) is one of the two standard ways people convert decimal to binary by hand; the interview section further down shows the other common way (repeatedly dividing by 2).
The same place-value test as Dart code, for the default input 13 (13 = 8 + 4 + 1 = 00001101), compared with Dart's built-in toRadixString / int.parse(..., radix: 2):
Bytes are the unit computers are built from, so storage sizes are counted in them. There is a subtlety worth knowing: officially a kilobyte (KB) = 1000 bytes, but for decades operating systems used 1024 bytes (because 1024 = 2¹⁰, a "round number" in binary) and still called it KB — the correct binary term is KiB (kibibyte), but "KB" showing 1024 is extremely common in practice (e.g. Windows' file sizes). This lesson (and this site's humanBytes exercise) uses the 1024 convention, since that's what you'll meet most often day to day.
| Unit | Bytes (1024 convention) | Roughly |
|---|---|---|
| 1 byte | 8 bits | one letter |
| 1 KB | 1024 bytes | half a page of plain text |
| 1 MB | 1024 KB = 1,048,576 bytes | a short mp3 clip |
| 1 GB | 1024 MB | a movie in decent quality |
The same table as code — each unit is a power of two (1 << 10 = 1024), unlike the SI "kilo" = 1000:
Even text and images are just numbers underneath. Every character has an assigned number — for example the capital letter 'A' is number 65 (part of a standard called Unicode, which includes the older ASCII numbering). An image is just a big grid of numbers, one small group of bytes (usually red, green, blue brightness) per pixel. There is no special "letter" or "colour" storage in a computer — it's numbers, all the way down.
'A'.codeUnitAt(0) returns 65 — you can check this yourself; it's verified in verify/d00.dart alongside every other claim on this page.3. Memory (RAM): numbered lockers
"Random Access" means the CPU can jump to any address in the same amount of time — locker 3 is exactly as fast to reach as locker 3,000,000. That's very different from a hard disk, which more closely resembles a filing cabinet where nearby drawers are faster to reach than far ones.
The lockers as Dart: the address is the list index (0–7 here). Every mem[i] is a single step wherever i is — that is "random access" — and a one-byte locker (Uint8List) can only hold 0–255, so 255 + 1 wraps to 0:
Input size → what's feasible: indexing is O(1) whether the list has 8 or 106 lockers; a loop that touches every locker of an n = 108 list is about one second of simple steps.
| RAM | Disk (SSD/HDD) | |
|---|---|---|
| Speed | Extremely fast (nanoseconds) | Much slower (microseconds–milliseconds) |
| Keeps data when powered off? | No — volatile | Yes — persistent |
| Used for | Currently running programs & their data | Long-term storage: apps, files, saved games |
The table's "volatile vs persistent" row in code — the file is still there after the program ends, the variable is not:
4. The CPU: fetch → decode → execute
Real CPUs understand only a tiny, rigid set of very simple instructions (this is called machine code). To see the idea without a real 200-instruction chip manual, here is a made-up tiny instruction set with only four instructions, one register called ACC (the accumulator — a small on-chip storage slot for the value currently being worked on) and a small piece of memory:
LOAD n— put the numberninto ACC (overwrite whatever was there).ADD n— addnto ACC.STORE addr— copy ACC into memory addressaddr.JUMP n— instead of moving to the next line, jump the bookmark (PC) straight to linen. This is how loops and "if / skip" logic are built at the machine level — there is no built-in "if" instruction, just conditional jumps.
Notice that instruction at line 3 (ADD 100) was never fetched at all — JUMP 4 overwrote the Program Counter so it skipped straight past it. This is exactly how a compiled if statement works under the hood: "if the condition is false, JUMP past this block."
Try it yourself: change the starting value that gets LOADed and watch the same fetch → decode → execute cycle run with different numbers, while the sequence of instructions (and the JUMP skip) stays identical.
The whole fetch → decode → execute loop for the program of Example 1, written as a Dart program (the 4-line print trace is exactly what the animation shows; ADD 100 never appears):
Input size → what's feasible: a real CPU does about 109 of these cycles per second; the Dart loop above costs one pass per executed instruction, so a 107-instruction run is fine (~0.1 s) and an endless JUMP loop never stops.
An if is just a conditional jump — when the condition is false, the Program Counter jumps past the block:
LOAD is really just a bit pattern like 00000001; the CPU's circuitry is physically wired so that pattern triggers the "copy this next number into ACC" behaviour. Decoding is electrical, not linguistic.As code: an instruction is only a number from a table.
5. From source code to machine code
You don't write raw LOAD/ADD instructions — you write readable source code like print('hi'). Something has to turn that into the machine code the CPU can fetch-decode-execute. There are two broad strategies:
- Compiler — reads the ENTIRE program up front and translates it all into machine code (or another language) before it ever runs, producing an output file.
- Interpreter — reads and carries out the program's instructions one at a time, right when it needs them, without producing a separate machine-code file first.
Dart actually uses a mix, depending on how you run it:
| Command | Strategy | Output | Trade-off |
|---|---|---|---|
dart run | Dart VM with a JIT compiler (Just-In-Time) | Machine code generated in memory, while running | Fast edit → run loop; Flutter's hot reload works because the VM can patch running code without a full restart |
dart compile exe / flutter build release | AOT compiler (Ahead-Of-Time) | A native executable, fully machine code, produced once before shipping | Fast startup, no compiler needed on the user's device — but the code is frozen, so no hot reload |
Web (dart2js) | Compiler, Dart → JavaScript | A .js file the browser's JS engine runs | Runs anywhere JS runs; JS engines then JIT-compile it again internally |
Web (dart2wasm) | Compiler, Dart → WebAssembly | A .wasm module | Often faster than JS for CPU-heavy code; still runs sandboxed inside the browser |
A compiler vs an interpreter on the page's mini language (the compiler's output is a list of numbers, the interpreter produces no file):
And a program can ask which mode it runs in (the flag is true only in an AOT release build):
Input size → what's feasible: JIT is for editing and running small programs many times a day; AOT pays its compile cost once, so ship it for apps that must start fast on millions of devices.
6. The stack vs the heap
List) are too big/flexible to live in a tray, so they live in a separate shared storage room called the heap, and the tray only holds a sticky-note with directions to the object — a reference (drawn below as an arrow).Example 1 — a function that builds and returns a list:
Example 2 — two variables pointing at the very same list, so mutating it through one is visible through the other:
Animation 8 as code, then the stack-vs-heap rules from this section (identical(a, b) asks "is it the very same object?"):
var b = a; does not copy a List — it copies the arrow. If you wanted an independent copy you need var b = List.from(a) (or [...a]). See the Medium "find the bug" question below.7. Everything in Dart is an object (preview)
In Dart, even an int like 5 or a bool like true is technically an object with methods you can call on it (try 5.isEven). The Dart VM optimises small numbers heavily so this doesn't cost what you'd expect, but conceptually there is no separate "primitive" world — everything is an object, and every variable is just a named reference: a labelled arrow that points at some value somewhere. For small numbers/booleans that arrow behaves so seamlessly you can mostly think of the variable as "holding" the value directly — but for anything bigger (a List, a Map, your own classes) remembering "it's a reference, not a box with a copy inside" is exactly what explains both animations in section 6.
Quiz
Interview questions
Cheat sheet
| Term | Meaning |
|---|---|
| Bit | One binary switch: 0 or 1 |
| Byte | 8 bits = 256 possible values |
| RAM | Fast, volatile, randomly-addressable working memory |
| PC (Program Counter) | Register holding the address of the next instruction to fetch |
| JIT | Compiles to machine code while the program runs (dart run) |
| AOT | Compiles to machine code once, ahead of time (dart compile exe, Flutter release) |
| Stack | Holds each function call's local variables as frames; last in, first out |
| Heap | Shared storage for objects; reached only through references |
| Garbage | A heap object nothing references any more |