In this chapter
We'll follow code from the words a programmer writes to the 0s and 1s a CPU runs: source code, machine code, assembly, high-level languages, and the three ways code gets translated — compilers, interpreters and JIT compilation.
The Problem in Real Life
Anna opens a file from BlueTicket's project called price.ts. Inside she finds lines like if (count >= 5) { return price * 0.9; }. It's almost English.
But she remembers Act 02: the CPU only understands tiny instructions made of 0s and 1s. "So who turns this into 0s and 1s?" she asks. John points at the screen. "That's the question. And the answer is different for different languages."
The CPU only speaks 0s and 1s. So how does it understand an if-statement?
Anna
The Code People Write vs. the Code CPUs Run
Two languages
People write readable code; CPUs run machine code. Something must translate between them.
Different ways to translate
Some languages translate everything before running, some translate while running, some do both.
From Source Code to Machine Code
A programming language is a language made for writing instructions for computers. Like a human language it has words and grammar, but it is strict: one missing bracket and the computer refuses to understand.
Source code is the text a programmer writes in a programming language — like Anna's price.ts file. People can read it and change it. The CPU cannot run it directly.
Machine code is the only thing a CPU can actually run: instructions made of binary numbers, like 10110000 01100001. Each kind of CPU (Intel/AMD, ARM in phones and new Macs) has its own machine code.
Between these two, there are levels:
- Assembly language — a thin, human-readable version of machine code. Instead of binary, it uses short words like
MOV(move a value),ADD(add) andJMP(jump to another instruction). Each line is usually one CPU instruction. A tool called an assembler turns it into machine code. Very few people write assembly today, but it shows what the CPU really does. - High-level languages — languages like Python, JavaScript, TypeScript, Java, C# and Go. One line can mean dozens of CPU instructions. They read almost like English and work the same on different CPUs. Almost all software today is written in high-level languages.
| Feature | Compiler | Interpreter | JIT |
|---|---|---|---|
| When translation happens | Before running | While running | While running, for busy code |
| Startup | Instant (already compiled) | Instant | Quick |
| Speed while running | Fastest | Slower | Fast after warming up |
| Mistakes found | Many before running | When the line runs | When the line runs |
| Examples | C, C++, Go, Rust | Python, Ruby, shell scripts | JavaScript (Node, Chrome), Java, C# |
| Level | What "add 2 and 3" looks like |
|---|---|
| High-level (JavaScript) | let total = 2 + 3; |
| Assembly | MOV EAX, 2 then ADD EAX, 3 |
| Machine code | 10111000 00000010 ... (binary the CPU runs) |
Three ways code reaches the CPU
Source code
what programmers write
Compiler
translate all first (C, Go, Rust)
Interpreter
translate line by line (Python)
JIT
interpret, then compile hot parts (JavaScript, Java)
Machine code
0s and 1s the CPU runs
This is source code. A person can read it, but the CPU can't run it directly — it must be translated first.
function groupPrice(count: number, priceCents: number): number {if (count >= 5) {return Math.round(priceCents * 0.9); // 10% off for groups}return priceCents;}
TypeScript becomes JavaScript, and Node.js then JIT-compiles the JavaScript into machine code while it runs.
So how does high-level code become machine code? There are three main ways:
1. Compiler. A compiler translates the whole program into machine code before it runs, and saves the result as a file you can run. Translation happens once; running is then very fast. If there's a mistake in the code, the compiler stops and tells you before anything runs. Languages: C, C++, Go, Rust. The downside: the result only runs on the kind of computer it was compiled for, and every change needs a new compile.
2. Interpreter. An interpreter reads the source code and runs it step by step, translating as it goes. There is no separate compile step — you just run the file. This makes trying things quick and easy, and the same code runs anywhere the interpreter is installed. The downside: it's usually slower, and some mistakes only show up when that line actually runs. Classic example: Python.
3. JIT (Just-In-Time) compilation. A mix of both. The program starts running quickly like an interpreter, but the runtime watches which parts are used most and compiles those into fast machine code while the program is running. JavaScript in Chrome and in Node.js works this way, and so do Java and C#. That's why modern JavaScript can be surprisingly fast.
BlueTicket's code is written in TypeScript — JavaScript with types added. TypeScript is first converted ("compiled") into plain JavaScript, and then Node.js runs that JavaScript with a JIT compiler. Two translation steps, both automatic.
Key Takeaway
People write source code in high-level languages; CPUs only run machine code. A compiler translates everything before running, an interpreter translates while running, and a JIT compiler starts by interpreting and then compiles the busiest parts on the fly.
Why This Matters
Which kind of language you use changes how you build, test and ship software. It explains why some errors appear when you build and others only when a user clicks a button; why some programs need a runtime installed and others don't; and why BlueTicket's TypeScript code needs a build step before it reaches the server. You'll make these trade-offs for the rest of your career.
Anna now knows her TypeScript code will be translated and run by Node.js. Which explains the error she's been staring at all morning: node: command not found. Her laptop doesn't have the thing that runs the code.
