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?

Think of a computer like a kitchen: input is groceries coming in (keyboard, mouse, touchscreen, network, sensors), processing is the chef following a recipe step-by-step (the CPU running instructions), and output is the finished dish leaving the kitchen (screen, speaker, file saved, network reply). Nothing happens unless something goes in, and nothing is useful unless something comes out.

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:

A computer is hardware (the body) running software (the instructions). Everything else in this lesson is about exactly HOW those instructions get carried out — bit by bit, address by address, instruction by instruction.

The same three words as three lines of Dart (run it: the output is shown in the comment):

2. Bits, bytes & binary counting

A bit is a single light switch: it can only be OFF (0) or ON (1). A computer has billions of these tiny switches (transistors). It cannot store letters, colours or sounds directly — everything must first be turned into patterns of 0s and 1s.

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 common mistake: thinking bit 0 is "the first bit" meaning the leftmost one. By convention bit 0 is the rightmost bit (the smallest place value, 1), just like the rightmost digit in decimal is the "ones" digit.

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.

UnitBytes (1024 convention)Roughly
1 byte8 bitsone letter
1 KB1024 byteshalf a page of plain text
1 MB1024 KB = 1,048,576 bytesa short mp3 clip
1 GB1024 MBa 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.

In Dart, '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

RAM (Random Access Memory) is like a long hallway of numbered lockers. Every locker holds one small chunk of data (a byte, or a group of bytes). Each locker has an address — a number — so the CPU can say "put 7 in locker 3" or "tell me what's in locker 6" and go straight there instantly, instead of searching locker by locker.

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

RAMDisk (SSD/HDD)
SpeedExtremely fast (nanoseconds)Much slower (microseconds–milliseconds)
Keeps data when powered off?No — volatileYes — persistent
Used forCurrently running programs & their dataLong-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:

RAM is fast because it's built from circuitry that can be read/written electronically in a fixed number of steps no matter the address (random access), and it's volatile because that circuitry needs continuous power to hold its state — cut the power and every locker resets to empty. That's why unsaved work disappears if your laptop dies.

4. The CPU: fetch → decode → execute

The CPU is the chef reading a recipe one line at a time. It keeps a bookmark (the Program Counter, or PC) pointing at the next recipe line. Every single step of every program you've ever run boils down to the CPU repeating three moves, forever: fetch the instruction the bookmark points at, decode it (figure out what it means), execute it (actually do it) — then move the bookmark and repeat.

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:

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:

A common mistake: thinking the CPU "understands" instructions the way a person reads English. It doesn't — 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:

Dart actually uses a mix, depending on how you run it:

CommandStrategyOutputTrade-off
dart runDart VM with a JIT compiler (Just-In-Time)Machine code generated in memory, while runningFast edit → run loop; Flutter's hot reload works because the VM can patch running code without a full restart
dart compile exe / flutter build releaseAOT compiler (Ahead-Of-Time)A native executable, fully machine code, produced once before shippingFast startup, no compiler needed on the user's device — but the code is frozen, so no hot reload
Web (dart2js)Compiler, Dart → JavaScriptA .js file the browser's JS engine runsRuns anywhere JS runs; JS engines then JIT-compile it again internally
Web (dart2wasm)Compiler, Dart → WebAssemblyA .wasm moduleOften 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.

"Flutter debug mode uses JIT, Flutter release mode uses AOT" is one of the most commonly asked Flutter interview facts — see the Hard-tier question bank below for exactly why hot reload disappears in release builds.

6. The stack vs the heap

Every time a function is called, it gets its own stack frame — think of a stack of trays in a cafeteria: each function call adds a new tray on top holding that function's local variables; when the function returns, its tray is lifted straight off the top. Objects (like a 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?"):

The single most common Dart/Flutter bug this causes: 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.
When a stack frame is popped, its local variables vanish — but any heap object they pointed to survives if something else (another variable, a longer-lived frame) still holds a reference to it. Only when the LAST reference to a heap object disappears does it become garbage — unreachable memory that Dart's garbage collector (GC) will eventually reclaim. We'll animate the GC itself in a later lesson; for now, just recognise the moment it happens.

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.

A variable is a NAME for a reference, not a box that contains a value. What the reference points to might live on the heap (objects) — that's the whole story behind "why did changing one variable also change the other one?" bugs.

Quiz

Interview questions

Cheat sheet

TermMeaning
BitOne binary switch: 0 or 1
Byte8 bits = 256 possible values
RAMFast, volatile, randomly-addressable working memory
PC (Program Counter)Register holding the address of the next instruction to fetch
JITCompiles to machine code while the program runs (dart run)
AOTCompiles to machine code once, ahead of time (dart compile exe, Flutter release)
StackHolds each function call's local variables as frames; last in, first out
HeapShared storage for objects; reached only through references
GarbageA heap object nothing references any more