Be Software Architecture Wise
Be Wise Apps

Leer en español · Lire en français · Auf Deutsch lesen

Section 0.1 of 0.8 · Beginner · 15 min read

Programs, code and how a computer runs them

Video lesson

4 min · narrated, with captions
Listen only (audio)
Read the transcript

Welcome to Be Software Architecture Wise. In this first lesson we start at the very beginning: what software is, and what really happens when a computer runs a program. You don't need any technical background. By the end, you'll know the handful of ideas that every later chapter builds on.

Let's begin with two words you'll hear all the time. Hardware is the physical machine: the processor, the memory, the storage and the network card. Software is the set of instructions and data that tell that hardware what to do. A useful picture is a restaurant kitchen. The chef is the processor, and the software is the recipes the chef follows.

A program is a list of precise instructions, and the computer follows them exactly, word for word. It never guesses what you meant. That's why mistakes in the instructions turn into bugs. The computer isn't being difficult; it's doing precisely what it was told. Programmers write those instructions as source code, in languages such as Python, Java or Go, which people can read and change.

But the processor can't read Python or Java. Its own language is machine code: very simple instructions, specific to one type of processor. So source code has to be translated. There are two main routes. In the first route, a compiler translates the whole program into machine code before it is released. Languages such as C, Rust and Go work this way. In the second route, the code is compiled into an in-between form called bytecode, and a program called a virtual machine runs it, translating busy parts into machine code as it goes. Java, Python and C sharp work like this.

Between your programs and the hardware sits the operating system, such as Windows, macOS, Linux or Android. Applications, like a web browser or a food delivery app, don't touch the hardware directly. They ask the operating system for what they need, through what are called system calls. The operating system shares the processor, the memory, the files and the network between all the programs that are running, and it keeps them from interfering with each other. In our kitchen, it's the manager deciding which recipe gets the chef's attention.

When a program runs, the operating system creates a process for it, with its own private memory. Inside one process there can be several threads, each doing a piece of work at the same time. Threads share the process's memory. That's useful, but if two threads change the same data at once, you get some of the hardest bugs to find. And notice the difference between memory and disk. Memory, or RAM, is fast but it's wiped when the program stops or the machine restarts. Anything that must not be lost has to be saved to durable storage, usually a database.

Let's put it together with a tiny program that works out the price of an order. When you start it, the operating system creates a process and starts the Python interpreter inside it. The interpreter reads the file and turns it into bytecode. If there's a typing mistake, such as a missing bracket, it's reported now, before anything runs. The program asks you how many items you want, and waits. While it waits, it uses no processor time at all. Then the calculation: three items at twelve pounds fifty is thirty seven pounds fifty, and adding twenty per cent VAT gives forty five pounds. Finally the result is printed and the process ends. Its memory is handed back, and because nothing was saved to disk, the answer is gone.

Why does this matter for software architecture? Because every system you'll design, however large, is made of programs running as processes on shared hardware. Decisions about what lives in memory and what must be stored durably come up again and again, in every chapter. And because many things run at once and machines restart, architects have to plan for concurrency and for failure from the start.

That's the foundation. The full section has the complete code example and more on the operating system and bugs. Next, in Section zero point two, we look at how programs organise data. See you there.

What you need to know

Hardware and software

A computer is a machine that follows instructions very quickly. The physical parts you could touch are the hardware; the instructions that tell those parts what to do are the software. A laptop, a phone, the server that runs your bank's website and the chip inside a smart thermostat are all computers built from the same four kinds of part.

The CPU (central processing unit, or processor) carries out instructions. A modern processor has several cores, each able to work through its own stream of instructions, and each core runs billions of simple steps per second. RAM (random-access memory, usually just called memory) holds the instructions and data the CPU is working on right now. It is fast, but it is volatile: switch the power off and its contents vanish. Storage (a solid-state drive or a spinning hard disk) keeps files when the power is off, but is much slower to reach. A network card (wired or Wi-Fi) sends and receives data to and from other computers.

Think of a restaurant kitchen. The chef is the CPU: the only one who actually cooks. The worktop is RAM: everything the chef is using at this moment is laid out there, within arm's reach, but it is cleared at the end of the night. The pantry and freezer are storage: large, permanent and a walk away. The hatch to the dining room is the network card, through which orders arrive and dishes leave. The recipes are the software.

The four parts every computer has

PartWhat it doesKitchen analogyWhat happens if it is the bottleneck
CPU (processor)Executes instructions: arithmetic, comparisons, moving dataThe chefWork queues up; pages and reports become slow even though there is spare memory
RAM (memory)Holds running programs and the data they are usingThe worktopThe system starts shuffling data to disk and slows sharply, or programs are stopped
Storage (SSD or disk)Keeps files and databases permanentlyThe pantry and freezerSaving and loading data becomes slow; a full disk can stop a database
Network cardSends and receives data to other machinesThe hatch to the dining roomRequests wait to be sent or received; users see delays

What a program is

A program is a list of precise instructions that a computer follows to do a job: calculate a bill, display a web page, send a payment. The word that matters is precise. A human cook can cope with "add salt to taste"; a computer cannot. It does exactly what it is told, in exactly the order it is told, and nothing more. If an instruction says to divide by a number that turns out to be zero, the computer does not shrug and carry on: the program stops with an error, unless the programmer wrote further instructions saying what to do in that case.

At the lowest level the CPU repeats one cycle for as long as it is switched on: fetch the next instruction from memory, decode what it means, execute it, and move on to the next one. Each instruction is tiny: add two numbers, compare two values, copy a value from one place in memory to another, jump to a different instruction if a comparison was true. Everything a computer does, from playing a film to training an AI model, is built from billions of these small steps.

Software is the broader term: programs together with the data, configuration and documentation they need. An application (or app) is software that does something useful for a person, such as a banking app. System software, such as the operating system described below, exists mainly to run other software.

Layers: applications, the operating system and the hardware

Applications ask the operating system for services, and the operating system controls the hardware: CPU, RAM, storage and network cardWeb browserFood-delivery appSpreadsheetsystem callsOperating systemschedules the CPU, shares memory, files, networkHardwareCPUruns instructionsRAMfast, temporaryStorageslow, permanentNetwork cardsends and receivesdrivers
Applications never drive the hardware directly: they ask the operating system, which shares the hardware fairly and safely between them.

Source code and programming languages

CPUs understand only machine code: instructions encoded as numbers, specific to a family of processors. Intel and AMD chips in most laptops and servers use one family (x86-64); the chips in phones, recent Macs and many cloud servers use another (Arm). Machine code that runs on one does not run on the other. Writing machine code by hand is slow, error-prone and almost never done today.

Instead, people write source code in a programming language: a precisely defined notation that reads partly like English and partly like mathematics. Python, JavaScript, Java, C#, Go, Rust and C are widely used examples. These are called high-level languages because they let a programmer say "add up the prices in this basket" in one or two lines instead of dozens of machine instructions, and because they hide details of the particular CPU. Source code is plain text, saved in files, and it is what programmers read, write, review and store in version control (a system that keeps a history of every change, explained in the last section of this chapter).

Languages differ in what they make easy. Python is valued for readability and is dominant in data science and AI work; JavaScript is the language every web browser runs; Java and C# are common in large business systems; Go is popular for network services; C and Rust are used where every microsecond and byte matters, such as operating systems and databases. No language is best at everything, and choosing one is partly a technical decision and partly a question of which skills a team has.

From source code to a running program: compilers, interpreters and virtual machines

Source code has to be turned into something the CPU can execute. There are three broad approaches, and real languages often mix them.

A compiler is a program that translates the whole of a program's source code into another form before it runs. Languages such as C, C++, Rust and Go are usually compiled ahead of time into machine code for a particular CPU and operating system. The result is an executable file that starts quickly and runs fast, but you must compile a separate version for each kind of machine you want to support, and the compiler rejects the program if it finds certain kinds of mistake.

An interpreter is a program that reads source code (or something close to it) and carries out its instructions as it goes, one step at a time. Interpretation makes it easy to try things out and to run the same program on any machine that has the interpreter, at the cost of speed.

Many popular languages sit in between. Java source code is compiled by a tool called javac into bytecode: compact instructions for an imaginary, idealised computer rather than for a real CPU. The Java Virtual Machine (JVM) then runs that bytecode on whatever real machine it is installed on, which is how one Java program runs on Windows, macOS and Linux unchanged. The JVM watches which parts of the program run most often and compiles those parts into machine code while the program is running, a technique called just-in-time (JIT) compilation. C# on .NET works in a similar way. The standard Python implementation, CPython, also compiles your .py files to bytecode (caching the bytecode of imported modules in .pyc files) and then runs that bytecode in its own interpreter loop.

Two routes from source code to the CPU

Two routes from source code to a running program: compiling straight to machine code, or compiling to bytecode that a virtual machine runsRoute 1: compiled ahead of time (C, C++, Rust, Go)Source code.c .rs .goCompilerbefore releaseMachine codefor one CPU typeCPUruns it directlyRoute 2: bytecode and a virtual machine (Java, Python, C#)Source code.java .pyCompilerjavac, CPythonBytecode.class .pycVirtualmachineCPUinterprets bytecode; may JIT-compilefrequently used parts to machine code
Compiling ahead of time produces machine code for one kind of CPU; compiling to bytecode lets one file run anywhere a suitable virtual machine is installed.

Comparing the approaches

ApproachTypical languagesStrengthsCosts
Ahead-of-time compilation to machine codeC, C++, Rust, GoFast start-up and execution; one self-contained file to deployBuild a separate version per CPU and operating system; slower edit-and-try cycle
Bytecode plus a virtual machine with JITJava, Kotlin, C#One build runs on many platforms; JIT can make long-running services very fastThe runtime must be installed; slower start-up and extra memory while the JIT warms up
Bytecode plus an interpreterPython (CPython), RubyQuick to write and try; very portableUsually slower for heavy computation; speed-critical parts are often written in C and called from Python
JIT-compiled in the browser or serverJavaScript (V8 in Chrome and Node.js)Runs everywhere a browser runs; fast enough for most web workPerformance can be less predictable than compiled code

The operating system's job

When you switch on a computer, the first substantial program to start is the operating system (OS): Windows, macOS, Linux, Android or iOS. Most servers on the internet run Linux. The operating system has four main jobs.

First, it shares the CPU. A laptop may have a few dozen programs running but only a handful of cores, so the OS switches each core between programs many times a second, fast enough that they all appear to run at once. Second, it manages memory, giving each program its own region and stopping one program from reading or overwriting another's. Third, it provides files and devices: programs ask the OS to open a file, draw on the screen or send data over the network, and the OS, through small programs called drivers, deals with the particular hardware. Fourth, it enforces security: users, permissions and the rule that an app may not read your photos unless you allow it.

Programs make these requests through system calls, a fixed set of services the OS offers. This is the first example in this course of an important architectural idea: a layer that hides messy details behind a stable set of requests. The person writing a food-delivery app does not need to know which brand of network chip the phone has.

Processes, threads, memory and disk

A program file sitting on disk is just stored instructions. When you start it, the OS creates a process: a running instance of the program with its own private memory, its own identity and its own share of CPU time. Open two browser windows and you may have several processes; start a web server and it may run as one process or many.

Inside a process there can be several threads. A thread is one sequence of instructions being carried out. A process with several threads can do several things at once, for example handle two customers' requests at the same time, and all its threads share the process's memory. That sharing is convenient but risky: if two threads change the same piece of data at the same moment without coordination, the result can be wrong in ways that appear only occasionally. These concurrency bugs are among the hardest to find, which is one reason many systems prefer separate processes, or designs where each request is handled independently.

The difference between memory and disk explains a great deal of software design. Data in RAM is quick to reach, roughly a tenth of a microsecond, but it disappears when the process ends, crashes or the machine restarts. Data on disk survives, but reaching it takes far longer: tens of microseconds or more on a solid-state drive, and several milliseconds on a spinning disk. So any information that must not be lost, such as an order or a payment, has to be written to durable storage, usually through a database, before the system tells the customer "done".

A process, its threads, its memory and the disk

A process with two threads sharing one block of memory, writing data it must keep to disk, beside a separate process with its own memoryProcess: the running order appThread 1handles customer AThread 2handles customer BMemory (RAM) of this processshared by both threads; lost on restartDisk (SSD)files and databasessurvive a restartsaveAnother processown, separate memory
Threads in one process share its memory; separate processes do not; only what is saved to disk survives a restart.
PRICE_PER_ITEM = 12.50   # pounds
VAT_RATE = 0.20          # 20 per cent

def total_with_vat(quantity):
    subtotal = quantity * PRICE_PER_ITEM
    return subtotal * (1 + VAT_RATE)

quantity = int(input("How many items? "))
print("Total to pay:", round(total_with_vat(quantity), 2))
A tiny Python program: work out the price of an order including VAT

Worked example

Worked example: what happens when the program runs

Suppose the code above is saved as order.py and you type python order.py, then enter 3.

1. StartThe operating system creates a new process, gives it memory and starts the Python interpreter (itself a compiled program) inside it.
2. CompileCPython reads order.py from disk, checks that it is valid Python and compiles it to bytecode. A missing bracket or colon would be reported here as a syntax error, before anything runs.
3. DefineThe interpreter runs the bytecode from the top. It stores 12.5 and 0.2 in memory under the names PRICE_PER_ITEM and VAT_RATE, and records the function total_with_vat without running it yet.
4. Inputinput() asks the OS to read from the keyboard. The process waits, using no CPU, until you type 3 and press Enter. int() turns the text "3" into the number 3.
5. CalculateThe function runs: 3 times 12.5 is 37.5, then 37.5 times 1.2 is 45.0. Each step is a handful of bytecode instructions, each of which becomes many machine instructions on the CPU.
6. Outputprint() asks the OS to show Total to pay: 45.0 on the screen.
7. FinishThe program reaches its last line, the process ends and the OS reclaims its memory. Nothing was saved to disk, so the result is gone unless you copied it.

Even this nine-line program relies on the operating system for memory, keyboard and screen, and on a language runtime to translate it for the CPU.

If you typed three instead of 3, int() would fail with a runtime error and the program would stop: the code never said what to do with bad input.

The program keeps money in a floating-point number to stay short. Real financial systems store whole pence or use a decimal type, for reasons the next section explains.

Bugs: when the instructions are wrong

A bug is a mistake in software that makes it behave differently from what was intended. Because computers follow instructions exactly, almost every bug is a gap between what the programmer meant and what the programmer actually wrote, or a situation nobody thought about.

It helps to recognise three kinds. A syntax error breaks the language's grammar (a missing bracket), so the compiler or interpreter refuses to run the program at all; these are found immediately. A runtime error happens while the program runs, such as dividing by zero or trying to read a file that does not exist; the program usually stops with an error message. A logic error is the most dangerous: the program runs happily and gives the wrong answer, for example charging VAT twice. Nothing crashes, so only testing, careful review or an unhappy customer will reveal it.

Professional teams expect bugs and build defences: automated tests, code review, gradual releases and monitoring that notices when error rates rise. Many of these defences are architectural, and they appear throughout this course.

Why this matters for architecture

Architecture is about the big decisions that are expensive to change, and several of them rest on the ideas in this section. The choice of language and runtime affects start-up time, memory use, which libraries you can use and whom you can hire. The split between memory and disk decides what must be stored durably, and how a system recovers after a crash. Processes and threads are the units that run, fail and scale: a later chapter asks whether a system should be one process or many cooperating ones. And the operating system's layered design, hiding hardware behind a stable set of requests, is the first instance of a principle you will meet again as information hiding.

The next section looks at what programs actually work on: data, and the structures and algorithms that make working with it fast or slow.

Check your understanding

Why does a program's data disappear when it crashes, unless it was saved?

While a program runs its data lives in RAM, which belongs to the process and is cleared when the process ends or the machine restarts. Only data written to storage such as a disk or database survives.

What is the difference between a compiler and an interpreter?

A compiler translates the whole program into another form (machine code or bytecode) before it runs; an interpreter carries out the instructions as it reads them. Many languages, such as Java and Python, compile to bytecode first and then run that bytecode in a virtual machine.

Why can a Java program built on a Windows laptop usually run unchanged on a Linux server?

Java is compiled to bytecode, not to machine code for one CPU. Any machine with a Java Virtual Machine installed can run that bytecode.

Why do programs ask the operating system to read a file or send data, instead of controlling the disk or network card themselves?

The operating system shares hardware safely between programs, enforces permissions and hides the details of each device behind system calls, so applications do not need to know which hardware they are running on.

Next · 0.2Data and algorithmsHow computers represent numbers and text, the everyday data structures (lists, dictionaries, queues, trees, graphs), what an algorithm is, and how Big-O explains why some systems slow down as data grows.Continue →

Sources and further reading

Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.

Ask: “If Python is slower than C, why do so many companies build their back-end systems in Python?”