Chapter 7

Three ways to run code

You have installed three languages. They look like three separate subjects. They are not. They are three answers to one question: when does your code get turned into instructions the processor can carry out?

The same program, three times

Here is the program from chapter 6, written three ways. Read them side by side. They do exactly the same thing.

#include <stdio.h>

int main()
{
    int num1;
    int num2;

    printf("To add two numbers.\n");

    printf("Enter the first number: ");
    scanf("%i", &num1);

    printf("Enter the second number: ");
    scanf("%i", &num2);

    num1 = num1 + num2;

    printf("The sum of two numbers is %i.\n", num1);

    return 0;
}
print("To add two numbers.")

num1 = int(input("Enter the first number: "))
num2 = int(input("Enter the second number: "))

num1 = num1 + num2

print(f"The sum of two numbers is {num1}.")
import java.util.Scanner;

public class Sum {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        System.out.println("To add two numbers.");

        System.out.print("Enter the first number: ");
        int num1 = in.nextInt();

        System.out.print("Enter the second number: ");
        int num2 = in.nextInt();

        num1 = num1 + num2;

        System.out.printf("The sum of two numbers is %d.%n", num1);
    }
}

All three print the same thing:

To add two numbers.
Enter the first number: 3
Enter the second number: 4
The sum of two numbers is 7.

But you start them differently

C needs two commands. One to build, one to run:

$ clang sum.c -o sum
$ ./sum

Python needs one. There is nothing to build:

$ python3 sum.py

Java needs two, but the second one is not your program — it is a machine that runs your program:

$ javac Sum.java
$ java Sum

Those small differences are the whole chapter. Here they are in one picture.

The three roads

Three roads from source code to the processor C translates everything before you run it. Java translates to bytecode before, and to machine code while it runs. Python translates everything while it runs. BEFORE you run it WHILE it runs C sum.c clang sum machine code CPU Java Sum.java javac Sum.class bytecode java the JVM, with JIT CPU Py sum.py nothing is built python3 reads it line by line CPU
The dashed line is the moment you press Enter. Everything to the left happens once, before the program starts. Everything to the right happens every time it runs. C sits entirely on the left, Python entirely on the right, and Java has a foot on each side.

Watch it explained

This four-minute video covers the same idea with animations. It opens in a new tab:

Watch: Python vs C++ vs Java

Two parts to skip

The video opens with about fifteen seconds where the author talks about his own books, and it ends with an advertisement for a newsletter. The four minutes in between are what matter.

It says C++ where we say C. For everything in this chapter, the two behave the same way: both are compiled all the way to machine code before you run them.

Follow this channel

The video comes from ByteByteGo: youtube.com/@ByteByteGo

It explains how real software is built, and how large systems work across many machines at once. The drawings are clear and the videos are short. For anyone learning to build software, it is one of the best places to watch.

A habit worth starting now, and keeping:

  • Watch one video every week.
  • Write short notes in your own words while you watch.
  • Then try the idea out yourself, even in a very small way.

Watching teaches you a little. Writing it down teaches you more. Trying it is what makes it stay.

C — translated once, before

clang turns your whole file into machine code and saves it. That finished file is the program. When you run it, no translating happens at all — the processor is handed instructions it already understands.

Two things follow from that, and you can see both.

The source is no longer needed. Copy just the program somewhere else and run it:

$ mkdir ~/ship && cp sum ~/ship/
$ cd ~/ship && ls
sum
$ ./sum
To add two numbers.

No sum.c in that folder, and it still runs. This is how most software is delivered: you get the machine code, not the source.

Change the source and nothing changes until you build again. The program is a photograph of your code, taken when you ran clang.

Python — translated as it goes

There is no build step, and no second file. python3 is a program that reads your file and does what each line says, one line at a time, every single time you run it.

$ ls
sum.py

One file in, nothing new out. That is why Python feels quick to work with: edit, run, edit, run, with nothing in between.

It also explains something from step 12. pdb can stop your program in the middle of a line and show you the value of a variable, because at that moment something is still reading your program. The source is right there, being followed.

The cost is paid every time

A loop that runs a million times has its lines worked out a million times. C worked them out once, before the program started.

That is where the speed difference below comes from.

Java — a bit of both

Java splits the difference, and this is why it needs two commands.

javac translates your source into bytecode. Bytecode is instructions, but not for your processor — for an imaginary machine that does not exist in hardware.

$ javac Sum.java
$ file Sum.class
Sum.class: compiled Java class data, version 65.0

You met that file already. In step 5 you looked at its first bytes:

$ xxd -l 8 Sum.class
00000000: cafe babe 0000 0041                      .......A

cafe babe is the marker that says "this is Java bytecode", the same way 7f 45 4c 46 says "this is a Linux program".

Then java starts the JVM — the Java Virtual Machine. It is a program that pretends to be that imaginary processor. It reads your bytecode and carries it out.

And it does one more thing. When it notices a piece of bytecode being used over and over, it translates that piece into real machine code, right then, and uses the fast version from that point on. This is called JIT, short for just-in-time compilation.

Why bother with an imaginary processor?

Because the same Sum.class runs anywhere a JVM exists — a laptop, a phone, a server — with no rebuilding. Your sum from C runs only on the kind of processor and system it was built for.

That portability is why Java is used for large systems that must run on many different machines.

What it costs

Same task in all three: add up every whole number from 1 to 10,000,000, and print the total. Measured on one machine, on the same day.

0.04s
C
already machine code
0.61s
Java
JVM start, then JIT
6.26s
Python
every line, every time

Python took about 150 times as long as C. Java landed between them, and a good part of its time was starting the JVM at all — on a longer job, JIT closes much of the gap.

Do not read this as "Python is bad"

This loop is the worst possible case for Python: ten million tiny steps and no real work in any of them. Most real programs spend their time waiting for a disk, a network or a person, where the language makes no difference at all.

Six seconds of computer time can easily save an hour of your time. That is often a good trade.

What each one leaves on the disk

LanguageYou writeBuild makesTo run it you need
C sum.c — 326 bytes sum — 16064 bytes nothing else
Java Sum.java — 471 bytes Sum.class — 899 bytes a JVM
Python sum.py — 188 bytes nothing python3, and the source

Notice the C program is fifty times bigger than its source. That is the C library joined in by the linker, as chapter 6 described.

The whole chapter in one table

CJavaPython
Translated whenbefore you runpartly before, partly duringwhile it runs
Calledcompiledbytecode + JITinterpreted
Build stepyesyesno
Speedfastestclose behindslowest
Runs on another machineonly the same kindanywhere with a JVManywhere with Python
Good forspeed, small hardwarelarge systems, many machinesgetting it working quickly

Other languages fit the same three shapes. Go and Rust are compiled like C. Ruby and JavaScript are interpreted like Python. C# is bytecode like Java. Learn the three shapes and a new language is far less new than it looks.

Try it

Build the C program, then remove the source and prove it still runs:

$ clang sum.c -o sum && mkdir -p ~/ship && cp sum ~/ship/
$ cd ~/ship && ls && ./sum

Now try the same trick with Python. Copy only the "program" — except there isn't one:

$ python3 /does/not/exist/sum.py

Look at what each build step produced:

$ file sum sum.c sum.py Sum.java Sum.class

And find the marker at the front of the Java bytecode:

$ xxd -l 8 Sum.class

You can now explain