Compilers and Interpreters: How Code Runs

The same marks, different ways to run the code

A teacher has written instructions that add maths and science marks. Saving the file does not automatically display a total. The computer needs software that can read and carry out the instructions in that language.

A compiler translates code into another representation. An interpreter carries out code or an intermediate representation while the program runs. A language implementation can combine these approaches, so the distinction describes tools and stages rather than two permanent categories of language.

  1. Compare the jobs

    Explain the difference between translating a program and carrying it out.

  2. Follow real workflows

    Identify what happens when a student marks program is run with Python or built with a C compiler.

  3. Diagnose a mistake

    Distinguish a source-code error from an incorrect result, and explain why successful compilation is not enough.

Two jobs that can work together

  1. Source program

    The teacher writes an addition rule in a particular language.

    maths + science
  2. Translation or preparation

    A language tool reads the source and may create another representation.

    Source → executable or intermediate code
  3. Execution

    A processor or language engine carries out the prepared instructions.

    Output: 75
An intermediate representation is code arranged for another tool to use. It is not necessarily native machine code.

Translation is different from execution

First revisit the journey from source code to the processor if the terms “source” or “machine instruction” are new. The student marks program gives us a task whose intended result is easy to check.

Translating means expressing instructions in a representation another tool can use. Executing means carrying out those instructions with actual values. A translated program may exist on disk before it has added a single mark.

An interpreter is software that performs the operations described by code. It can work with source code or prepared instructions. It is misleading to define all interpreters as tools that read one physical source line at a time: preparation, whole-file checks and intermediate code can be involved.

Start with the actual student marks program

This is JavaScript, using the same school task as the first lesson. const stores a named value, + adds the marks, and console.log displays the total. The expected result is 75, whether the language engine uses interpretation, compilation or a combination.

This example shows the program’s behavior. The next sections compare real tools that carry out equivalent instructions in other languages; they do not pretend that converting a text value to a number is compilation.

JavaScript · local teaching example

const maths = 40;
const science = 35;
const total = maths + science;
console.log(total);

Expected output

75

A real compiled workflow: build a C program

Here is the same addition in C. Save this complete source as student_total.c. The int declarations store whole-number marks. main is where this program begins. printf displays the total, and %d requests a whole-number representation. The included standard header declares the output function. In the output string, \n starts a new line; return 0 reports successful completion to the surrounding system.

#include <stdio.h>

int main(void) {
  int maths = 40;
  int science = 35;
  int total = maths + science;
  printf("%d\n", total);
  return 0;
}

In a Unix-like terminal with a C compiler available as cc, these are the shell commands to build and run it:

cc student_total.c -o student_total
./student_total

Expected output from running the executable:

75

The first command compiles and links the program, producing the executable named student_total. Linking connects the program with required library facilities. The second command runs the executable. Successful building normally does not print the student’s total; execution does.

This is an ahead-of-time workflow: build before running. The executable targets a particular platform; copying it to an arbitrary different operating system or processor is not a guarantee it will run. Commands and executable names vary by environment. Here -o names the output file. For one widely used implementation, see Clang’s compilation and linking stages.

A real interpreted workflow: run the Python source

This version uses Python. Save it as student_total.py. Assignment stores the marks, + adds them, and print displays the result. Python does not use JavaScript’s const keyword.

maths = 40
science = 35
total = maths + science
print(total)

With Python 3 available as python3, run this shell command:

python3 student_total.py

Expected output:

75

You give the source file to the Python implementation instead of separately building a native executable. CPython, the commonly used implementation, first compiles source into bytecode—instructions for its own execution engine—and then carries out that bytecode.

The implementation still runs on real hardware. Bytecode instructions and CPU machine instructions are different instruction sets. See Python’s definition of bytecode for this distinction. The commands here are examples to run on your own configured computer; the lesson is not an installed C or Python environment.

Why Python can be both compiled and interpreted

The Python program is reproduced here because we are now looking at its preparation stage. It still adds the marks and prints 75 when executed.

maths = 40
science = 35
total = maths + science
print(total)

With the file saved, the shell command below asks Python to compile it without running its top-level instructions:

python3 -m py_compile student_total.py

Expected terminal output on successful compilation: none. A bytecode cache file is created under __pycache__. The absence of 75 matters: preparing instructions is different from executing print(total).

That .pyc file is not a standalone native executable. Python bytecode is tied to its implementation and version. Cached bytecode can avoid some preparation when loading a module; it does not make the addition rule intrinsically faster. Python also treats a script run directly from the command line differently from an imported module. See Python’s explanation of compiled files and caching.

Follow CPython’s two stages

  1. Python source

    Readable instructions describe the marks calculation.

    total = maths + science
  2. Bytecode

    Compilation prepares instructions for the Python engine.

    Not a native executable
  3. Execution engine

    CPython carries out those instructions and calls print.

    75
This describes CPython at a beginner level. It does not claim that all Python implementations use identical internals.

Compilation can also happen while a program runs

JavaScript · the task stays the same across engine strategies

const maths = 40;
const science = 35;
const total = maths + science;
console.log(total);

Expected output

75

Just-in-time compilation, usually shortened to JIT, translates code during execution rather than only in a separate build step. A language engine can start with one execution strategy and compile selected code as it runs. Different engines make different choices.

JavaScript engines commonly use a combination of techniques. That is why calling JavaScript “only interpreted” does not describe every engine. See the documentation on just-in-time compilation.

For our small marks calculation, the important result is still 75. We have not measured its speed, and this diagram does not expose a particular engine’s internal instructions. Choose a tool for the whole project—its platform, libraries and development workflow—rather than assuming one execution label guarantees better performance.

Decide what happens before the total appears

A teacher saves the Python source shown here, then runs only python3 -m py_compile student_total.py. She sees no total and thinks addition has failed. What should you explain? What command would actually run the calculation?

maths = 40
science = 35
total = maths + science
print(total)

Worked answer

The command prepared bytecode; it did not execute the addition and print instruction. No output is expected on successful compilation. Run python3 student_total.py to execute the source and display 75.

Now change science to 20 and run the source again. The total becomes 60 because the input changed. No change to the language’s execution model is needed. If using the C workflow, rebuild its executable after changing its source; an existing executable does not automatically absorb source edits.

Successful compilation does not prove the answer is right

Consider this complete JavaScript variation. It is valid code, but the teacher asked for the combined marks.

const maths = 40;
const science = 35;
const total = maths - science;
console.log(total);

Expected output: 5. Translation or execution can succeed while the rule is wrong. This is a logic error: the chosen instructions do not solve the intended task.

A missing closing bracket can instead be a syntax error, meaning the source breaks the language’s grammar. Some checks happen before execution; other failures appear only on particular paths while running. Do not assume every interpreter will first print every earlier line, or that every compiler can find every possible error.

Use tests and code reviews to compare actual results with requirements, and learn more in the lesson on syntax, runtime and logic errors.

Check: compile, run or both?

What best describes the CPython workflow shown in this lesson?
Choose one answer. You can try again.

Answer and explanation

CPython compiles source to bytecode and its execution engine carries out that bytecode.

Yes. Compilation and interpretation can be stages of the same implementation.

Check: changing the compiled school program

A C program was built with maths 40 and science 35. You edit its source to science 20, but run the previously built executable without rebuilding. What should you expect?
Choose one answer. You can try again.

Answer and explanation

It still prints 75; rebuild the source to produce an executable with the changed mark.

Yes. In this example the marks are fixed in the source, so rebuilding is needed to incorporate the change. Reading marks from an external file would be a different program.

Keep the distinction between preparing and running

A compiler translates code; an interpreter carries out code or prepared instructions. An implementation can use both. Ahead-of-time compilation happens before execution, while JIT compilation happens during it.

The C workflow builds an executable and then runs it. The CPython workflow prepares bytecode and carries it out. Compiling the Python file by itself prints no marks total. These are specific examples, not universal rules about every language implementation.

Try explaining why a successful build can still produce 5 when the expected total is 75. Next, compare ways to organize the same student marks program. If you want hands-on language practice, use the Python course.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Programming Paradigms and Styles →