# Part V: Software — Instructions and languagesEvery program eventually becomes machine code. Languages and translators sit between human thought and hardware.

## Machine code

At the end of the previous section we introduced what machine code was: patterns of bits that trigger specific, pre-wired physical behavior inside the CPU. But humans rarely create software at that level. We describe what we want using languages designed around ideas, structure, and intent, while other software translates those descriptions into instructions the hardware can execute. The following section explains how every program ultimately becomes bits stored in memory and executed by the CPU.

### Machine code is just data

Machine code is just sequences of bits, which only do something when they reach the CPU and get executed.

Here is a tiny fragment of real x86-64 machine code — seven bytes that add two numbers and return the result. To the CPU these are voltages in memory; to us they are usually written in hexadecimal:

```text
48 89 f8    48 01 f0    c3
```

Those bytes mean nothing until the CPU fetches them and treats each pattern as an instruction. The next section shows the human-readable form of the same sequence.

The CPU has a very limited memory (registers, which are small and fast), so it only stores the machine code needed for execution. That's why all those bits, before reaching the CPU, need to be first stored somewhere in memory (RAM). How does the CPU know when to fetch those bits to execute them?

### Execution is a continuous fetch from memory

Execution works as a simple, repetitive process:

1. The CPU holds a number called the program counter (also called the instruction pointer). That number is an address in memory (RAM)
2. The CPU fetches the instruction (machine code) stored at that address
3. The instruction is decoded and executed
4. The program counter is updated
5. The process repeats

This happens billions of times per second. The CPU only holds the instruction it is executing right now, and enough information to execute the next one. Everything else remains in memory.

When we want to run a new program, how does the CPU know where to look? The CPU does not know what a program is, or when a user clicks a mouse. The CPU only knows one thing: the current value of the instruction pointer. So how does that value ever change in a meaningful way? Because some machine code (part of the operating system) is already running in the CPU, and that code is allowed to change the instruction pointer.

If the operating system (OS) determines that a new program should start running, it updates the instruction pointer to point at the first instruction of that new program. What we call "running a program" is simply the CPU being directed (by other machine code, part of the OS) to start fetching instructions from a different region of memory.

### Humans don't write machine code

While machine code is the only language the CPU understands, it is not a language suitable for humans. Machine code is unintuitive and difficult for humans to read or write. For this reason, higher levels of programming are developed to make software easier to write and understand. The first step in that direction is a human-readable form of machine code.

## Assembly

### Human-readable machine code

Assembly language is a symbolic representation of machine code. It replaces binary instructions with mnemonic names. Each assembly instruction corresponds directly to a machine code instruction.

The same seven-byte fragment from above, shown as machine code and as assembly. Both describe one function: take two arguments, add them, and return the result (x86-64, System V calling convention: arguments in `rdi` and `rsi`, return value in `rax`):

{{< tabs "atoms-machine-vs-assembly" >}}
{{< tab "Machine code" >}}
```text
48 89 f8      ; copy first argument into the return register
48 01 f0      ; add the second argument
c3            ; return
```
{{< /tab >}}
{{< tab "Assembly" >}}
```asm
mov  rax, rdi    ; first argument → return register
add  rax, rsi    ; add second argument
ret              ; return
```
{{< /tab >}}
{{< /tabs >}}

Each assembly line maps to exactly one machine-code instruction. The assembler is just a translator: it replaces mnemonics and register names with the corresponding byte patterns.

Assembly code is not executed by the CPU. It must first be translated by an assembler, a program that converts mnemonics and labels into their exact corresponding machine code.

### Uses and limitations

Assembly language provides complete and explicit control over the machine. Programs written in assembly manage registers, memory access, and control flow directly, without any implicit abstractions. This enables extremely precise and predictable program behavior.

The main limitations are that assembly programs are still difficult to write, difficult to maintain, and highly sensitive to errors. Additionally, machine code and assembly code are tightly coupled to a specific processor architecture (e.g. assembly code written for Intel CPUs won't work for Apple Silicon chips or Qualcomm chips found in phones).

Today, assembly language is used primarily in domains where its unique properties are indispensable:

- Critical parts of operating systems
- Embedded systems & hardware control: in environments with severe resource constraints or strict timing requirements (medical devices, microcontrollers, automotive systems, etc.)
- Performance-critical scenarios: Small parts of assembly inside larger programs (cryptography, multimedia processing, scientific computing, etc.)
- Security and reverse engineering: analyzing compiled programs, malware, and vulnerabilities

In conclusion, assembly improves readability, but it does not change the fundamental problem: every instruction still corresponds directly to a hardware operation. Machine code and assembly precisely describe what the hardware should do, but they are terrible at expressing why. The goal is not to eliminate machine code (since the CPU ultimately requires it), the goal is to add new layers that are better suited to human thought.

These layers allow programs to be written in terms of rules, structure, and intent, without requiring the programmer to manage individual registers or instructions, and without tying the program to a specific machine. These layers are called high-level programming languages.

## High-level programming languages

### Why programming languages exist

Programming languages are formal systems for describing computation in a way that humans can write, read, and reason about. Unlike machine code or assembly, programming languages are not designed around the constraints of hardware. They are designed around the constraints of human cognition: clarity, structure, reuse, and abstraction. A programming language allows a programmer to express intent (i.e. what a program should do) without specifying every mechanical hardware step required to do it.

### Common features of programming languages

Almost all high-level programming languages share common ideas and building blocks. Programming languages exist to describe behavior over time, so they almost always provide the following mechanisms:

- **Variables and data:** Programs need a way to name and store values: numbers, text, booleans, lists and more complex structures. The syntax and rules can vary between languages, but the idea is the same.
- **Operations and expressions:** Languages provide ways to compute new values from existing ones: arithmetic, comparisons, logical operations, function calls, etc. These expressions eventually translate into machine instructions, even if that translation is hidden from the programmer.
- **Control flow:** All non-trivial programs need to make decisions and repeat actions. Things like conditionals ("if this, do that") and loops ("do this until…") appear in nearly every language.
- **Execution order and sequencing:** Programs describe steps that need to happen in a specific order. Even when it isn't written explicitly, every program needs to define things like: what happens first, what depends on what, what must finish before something else starts, etc.
- **Abstraction and reuse:** Programming languages allow groups of behavior to be defined once and reused multiple times. This can take the form of functions that compute a result, procedures that perform a task, or larger modular units that encapsulate related behavior. For example, instead of writing the same steps to calculate a tax or validate an email address in many places, a programmer can define that logic once and reuse it throughout the program.

  This ability is essential for managing complexity in larger systems.

- **Interaction with the outside world:** Languages provide ways to express interaction with external systems. A program may read data from a file, send a request over the network, display information on a screen, or receive input from a user. Languages provide standardized ways to express these actions, even though the actual work is performed by the operating system and hardware underneath.

The same small program — name a value, decide what to do, repeat an action, and reuse that logic as a function — written in four common languages:

{{< tabs "atoms-high-level-languages" >}}
{{< tab "Python" >}}
```python
def greet(name, times):
    message = f"Hello, {name}!"
    for _ in range(times):
        print(message)

greet("Ada", 3)
```
{{< /tab >}}
{{< tab "C" >}}
```c
#include <stdio.h>

void greet(const char *name, int times) {
    for (int i = 0; i < times; i++) {
        printf("Hello, %s!\n", name);
    }
}

int main(void) {
    greet("Ada", 3);
    return 0;
}
```
{{< /tab >}}
{{< tab "JavaScript" >}}
```js
function greet(name, times) {
  const message = `Hello, ${name}!`;
  for (let i = 0; i < times; i++) {
    console.log(message);
  }
}

greet("Ada", 3);
```
{{< /tab >}}
{{< tab "Rust" >}}
```rust
fn greet(name: &str, times: u32) {
    let message = format!("Hello, {name}!");
    for _ in 0..times {
        println!("{message}");
    }
}

fn main() {
    greet("Ada", 3);
}
```
{{< /tab >}}
{{< /tabs >}}

The surface syntax differs, but each version uses the same building blocks: a named value, a loop, a reusable function, and an interaction with the outside world (printing).

### Differences within programming languages

While most high-level programming languages share the same basic building blocks, they differ in important ways. These differences influence how programs are written, how errors are handled, how fast programs run, and what kinds of problems a language is best suited for. These are some ways programming languages can differ from one another:

- **Level of abstraction and control:** Some languages allow programmers to manage low-level details such as memory layout, pointers, and explicit resource management, giving the programmer precise control and high performance, but also placing more responsibility on the programmer (e.g. C, C++ and Rust). Other languages hide most hardware details and manage memory automatically, prioritizing ease of use (e.g. Python, Java and JavaScript).
- **Safety vs. flexibility:** Some languages are designed to prevent many types of errors by enforcing strict rules and checks before a program can run. This favors correctness and long-term reliability (e.g. Rust and Java). Other languages prioritize flexibility and speed of development, allowing many decisions to be made while the program is running. This makes experimentation easier, but allows more errors (e.g. Python, Ruby and JavaScript).
- **Programming paradigm:** This defines what the main building blocks of a program are, how they interact, and the ways the programmer frames and solves problems in code. At a high level, paradigms are often divided into imperative and declarative styles. In imperative programming, the programmer describes how a computation is performed, step by step. In declarative programming, the programmer describes what result is desired, and the language figures out how to produce it.

  Some languages are built around a procedural paradigm (imperative style), where a program is mainly described as a sequence of steps and function calls that operate on shared data (e.g. C). Other languages are centered on an object-oriented paradigm, where programs are organized as collections of objects that combine data with the operations that act on that data. Java and C# are strongly shaped by this model.

  Some languages emphasize a functional paradigm, where computation is expressed as the evaluation and composition of functions, and where changing shared state is minimized or avoided (e.g. Haskell is a pure example, Scala also incorporates these ideas). Most modern languages are multi-paradigm, so they allow several styles at once. However, each language usually has a "default mindset" that feels most natural and that shapes how programs are typically written.

- **Execution model:** Some languages are designed to produce fast, predictable machine code, making them suitable for systems programming, real-time constraints, or performance-critical software (e.g. C, C++ and Rust). Others trade raw performance for portability, safety, and developer convenience, relying on additional software layers during execution (e.g. Java, Python and JavaScript).
- **Ecosystem and typical use cases:** Some languages evolve strong ecosystems around specific problem domains, such as JavaScript for web development, or R for statistics. Others aim to be broadly applicable across many domains, such as C++, Java and Python.

These differences explain why programming languages feel so different despite sharing the same underlying foundations. A language is not just a syntax, it's a collection of design decisions that favor certain ways of thinking, certain kinds of problems, and certain trade-offs between control and convenience.

See also: [List of programming languages by type](https://en.wikipedia.org/wiki/List_of_programming_languages_by_type)

### From programming languages to machine code

The CPU never runs languages directly, it runs instructions written in machine code. Therefore, programs written in a programming language must be translated into machine code (directly or indirectly) before they can run. This translation is performed by other programs, such as compilers (i.e. translate the program beforehand, and then feed that machine code to the CPU), interpreters (the CPU runs the interpreter's machine code, and the interpreter is responsible for understanding the program and making it happen), or a combination of both. This translation process can follow different approaches:

- **Direct native compilation (ahead-of-time):** Source code is translated entirely before execution into machine code for a specific CPU and operating system. The operating system loads this machine code into memory, and the CPU executes that machine code directly. Some languages that use this approach are C, C++, Rust, Go and Swift.
- **Pure interpretation:** The program is never directly translated into machine code. Instead, an already-compiled interpreter, which is running in the CPU, reads the source program and executes its meaning step by step. The CPU runs only the interpreter's machine code, while the program itself remains ordinary data. This approach was common in early languages such as Lisp and BASIC, and although it is rarer today for performance reasons.
- **Hybrid approaches:** The program is first compiled into an intermediate form (often called bytecode). This intermediate code is then executed by a separate software system (the virtual machine or VM), which itself is a normal machine code program running on the CPU. The VM can interpret this intermediate code (interpreter), compile parts of it into machine code just before execution (just-in-time compilation), or combine both techniques.

  Many modern languages use this model, with some (such as Java, C# and Kotlin) leaning toward just-in-time (JIT) compilation for performance, and others (such as Python, JavaScript, PHP, Lua, R and Ruby) leaning more toward interpretation with limited or optional JIT compilation. Overall, hybrid approaches form a broad spectrum.

Finally, it is worth mentioning that not all widely used "languages" are programming languages in the sense described above. Languages such as HTML and CSS are declarative description languages: they describe structure and appearance, and are interpreted by applications like web browsers, not executed by the CPU as programs. SQL is a query language whose statements are interpreted by a database engine, which then decides how to access and process data.

Bash and PowerShell are command and scripting languages that primarily orchestrate other programs. In all these cases, the CPU still ends up executing some machine code, but it is the machine code of the browser, database engine, or operating system component, not of the language itself. These languages act as instructions to software, rather than instructions to the CPU directly.

Regardless of the path taken, all execution eventually converges to the same place. Every program that runs on a computer ultimately becomes machine code.

### The layers above and below

At this point, everything that runs on a computer has been reduced to the same thing: machine code executing in the CPU. But, if all programs are just machine code, who decides when the CPU should stop executing one sequence of instructions and start executing another? The CPU itself has no notion of programs, users, files, or devices. Those concepts must be implemented by software that is already running in the CPU and is allowed to control execution. That software is what we call the operating system.
