From Source Code to Machine Code

2.The CPU Only Speaks 0s and 1s

A

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.

14–16 min

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

A

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) and JMP (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.
Table — Compiler vs. interpreter vs. JIT
FeatureCompilerInterpreterJIT
When translation happensBefore runningWhile runningWhile running, for busy code
StartupInstant (already compiled)InstantQuick
Speed while runningFastestSlowerFast after warming up
Mistakes foundMany before runningWhen the line runsWhen the line runs
ExamplesC, C++, Go, RustPython, Ruby, shell scriptsJavaScript (Node, Chrome), Java, C#
Table — The same idea at three levels
LevelWhat "add 2 and 3" looks like
High-level (JavaScript)let total = 2 + 3;
AssemblyMOV EAX, 2 then ADD EAX, 3
Machine code10111000 00000010 ... (binary the CPU runs)

Three ways code reaches the CPU

Source code

what programmers write

translated by

Compiler

translate all first (C, Go, Rust)

Interpreter

translate line by line (Python)

JIT

interpret, then compile hot parts (JavaScript, Java)

becomes

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.

A tiny piece of BlueTicket-style TypeScript
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.

Next