A compiler translates a program into another representation. An interpreter executes a program by carrying out its instructions. That is the central difference—but real programming environments often use both. Understanding the steps between source code and output is more useful than labeling a language “compiled” or “interpreted.”
Imagine writing a loop that adds the numbers from 1 to 4. You can express that calculation in C or Python and get the same answer: 10. What changes is how your tools prepare the instructions, execute them, and report a mistake. We will follow that small calculation through both workflows, then connect the idea to the browser compiler in Circuit.
What is a compiler?
A compiler is software that translates source code into a target form while checking the rules required for that translation. The target can be native machine code, assembly, bytecode, or another language. “Compiler” describes the translation job; it does not promise that the result is always an executable file or that the file runs on every computer.
In a typical desktop C workflow, GCC or Clang participates in a build that produces a program for a particular platform. The build and the execution are separate events. You can build today and run the resulting executable later, without supplying the original source to the compiler again.
This is often called ahead-of-time compilation: translation happens before the application runs. A compiler can also run during execution, which we will discuss under JIT compilation. Keep those two timings distinct rather than making “before execution” part of every possible compiler’s definition.
The compiler is also separate from your editor. VS Code helps you write source and launch commands; installing it does not automatically install a C toolchain. Our VS Code compiler setup guide shows how to verify those connections individually.
What is an interpreter?
An interpreter is a program that performs the operations described by another program. It may work with source structures or with an intermediate instruction format. It reads those instructions, tracks the program’s state, and carries out actions such as calculating a value, calling a function, or selecting the next instruction.
“It executes code line by line” is a tempting introduction, but a text line is not the unit that defines interpretation. One statement can span several lines; one line can contain several operations. A loop revisits instructions, a function call changes the execution path, and an interpreter may never execute the original source text directly.
In a common Python workflow, you ask an installed Python implementation to run a .py file rather than building a separate native executable yourself. An interactive prompt also lets you submit a small expression and inspect its result. Neither workflow means that parsing or compilation is absent from the implementation.
Compiler vs interpreter: compare jobs, not slogans
On narrow screens, scroll the table sideways to compare both columns.
| Question | Compiler’s role | Interpreter’s role |
|---|---|---|
| Main job | Translate a program into a target representation. | Carry out a program’s instructions. |
| Typical result | Code or an intermediate artifact used by another stage. | Executed operations and their effects, such as terminal output. |
| When it runs | Before execution in an AOT build; during execution in a JIT system. | While the program is being executed. |
| What it checks | Rules needed for translation; often additional warnings. | Operations as they execute, with checks determined by its runtime. |
| What runs on the CPU | Native output can run on the processor; bytecode needs a compatible execution system. | The interpreter’s own machine instructions implement the interpreted operations. |
| Can they coexist? | Yes: a compiler can produce instructions an interpreter consumes. | Yes: an interpreter can execute bytecode or work alongside a JIT compiler. |
A practical question is therefore: what representation do I have, and which tool consumes it next? A .c source file, an object file, a native executable, and a bytecode file are different artifacts. Knowing their roles helps explain why opening a source file is not the same action as launching a program.
A C example: build once, then execute the result
Save this as total.c. It uses four loop iterations to compute 1 + 2 + 3 + 4.
#include <stdio.h>
int main() {
const int limit = 4;
int total = 0;
for (int number = 1; number <= limit; number++) {
total += number;
}
printf("Total: %d\n", total);
return 0;
}With GCC installed on Linux, the following commands build the program and then launch it. Run the second command only after the first succeeds.
gcc -std=c17 -Wall -Wextra total.c -o total
./totalThe expected output is Total: 10. On Windows with GCC, name the output total.exe and launch it from PowerShell using .\total.exe. On macOS with Apple’s toolchain, use clang for the build. The installation guide above provides the platform-specific setup.
What happened during the build?
- Preprocessing handles directives such as includes and macros.
- Compilation translates the prepared C source toward the target’s instructions.
- Assembly produces an object file in a conventional native GCC build.
- Linking resolves references and connects the required objects and libraries.
GCC’s official output-options reference describes these stages. Its -c option stops before linking. Our command performs the full build; you do not need to manage each intermediate file for this single-source exercise.
Now change limit from 4 to 5 and save the source. Launching the old executable still runs its earlier calculation. Rebuild successfully, then launch the new result to get 15. Editing the source does not reach backward and rewrite an executable that already exists.
A Python example: the same calculation, a different workflow
Save the equivalent calculation as total.py:
limit = 4
total = 0
for number in range(1, limit + 1):
total += number
print(f"Total: {total}")On a system where Python 3 is available as python3, run python3 total.py. If your installation uses python instead, first check python --version and confirm it is Python 3. Circuit’s editor runs C, so execute this Python example in a suitable Python environment.
This also prints Total: 10. The range ends before its second argument, so limit + 1 includes 4. Changing limit to 5 and running the file again gives 15, without an explicit native build command in this workflow.
But CPython compiles code too
CPython, the implementation commonly downloaded from python.org, compiles Python source to bytecode and executes that intermediate form. Bytecode consists of instructions for its execution system rather than a native executable you launch independently. The Python glossary documents this translation and the bytecode caches used for imported modules.
A .pyc cache can avoid repeating source-to-bytecode compilation when it is valid. It is not a general-purpose native executable, and Python bytecode is not guaranteed to remain compatible across releases. “No separate build command” and “no compilation occurs” are different claims.
Errors: what is checked, and when?
Consider three mistakes in the C example. They happen at different stages and require different responses.
- Missing punctuation: remove the semicolon after the total declaration. The translation fails with a diagnostic; restore it and rebuild.
- Incorrect loop condition: change number <= limit to number < limit. This valid program prints 6 for a limit of 4. Compilation success does not certify your intended calculation.
- An invalid operation during a run: a bad pointer or an out-of-bounds access in a different program may cause a failure or other undefined behavior. A compiler cannot be assumed to catch every such mistake.
Interpreted workflows have preparation errors too. If a Python file contains invalid syntax, CPython can reject that file before executing its first statement. An interactive prompt parses the separately submitted units, which is a different situation from loading a whole script.
By contrast, this valid Python source begins execution and then raises an exception when it attempts the division:
print("Starting calculation")
count = 0
print(12 / count)The first print executes; the division raises ZeroDivisionError. Python’s errors and exceptions tutorial distinguishes syntax problems from exceptions during execution. Do not use error timing alone to decide whether a tool contains a compiler or an interpreter.
In C, read the first meaningful diagnostic, identify whether the build or the run failed, and test the intended result separately. The compiler error guide and memory failure guide explain those investigations with C examples.
Bytecode and JIT: why real systems mix the approaches
An intermediate representation separates the language’s source syntax from the execution mechanism. A bytecode compiler can handle source translation, while an interpreter handles the resulting instruction stream. This is a pipeline with two different jobs, rather than a contradiction.
A just-in-time compiler, or JIT, generates code while a program is running. A runtime may initially interpret instructions, observe frequently executed regions, and compile selected parts to machine code. It can retain an interpreter for other parts. Not every implementation uses this strategy, and its decisions depend on the runtime.
V8 is a concrete example of an engine with both an interpreter and compiler components. Its documentation describes the Ignition interpreter and TurboFan optimizing compiler. Calling JavaScript simply “interpreted” leaves out how an engine can prepare and optimize its execution.
The language defines the rules your program follows; an implementation chooses how to carry them out. “C is usually compiled ahead of time” describes a familiar workflow. It is more accurate than treating compilation as an immutable property of every possible C implementation.
Speed and portability depend on the complete environment
What does “faster” measure?
Native code can avoid the repeated instruction-dispatch work of a simple interpreter. But total performance also includes startup, preparation, optimization, allocation, libraries, and the algorithm itself. A program waiting for input or a network reply is not mainly limited by arithmetic execution speed.
For a tiny script run once, preparation costs matter. For a calculation repeated many times, execution costs may matter more. A JIT can spend time optimizing before benefiting from its generated code. Measure the actual task in the environment you plan to use rather than ranking every program by its language label.
Which file can you move to another computer?
Portable source still depends on language rules and available libraries. A native binary usually targets a particular combination of processor architecture and operating-system conventions; it does not become portable merely because the source was written in C.
A script or bytecode artifact needs a compatible runtime and dependencies on its destination. Code that opens an operating-system-specific path can fail even when the language is supported there. WebAssembly supplies another target with its own host environment; its portability documentation explains how embedding support affects that environment.
What happens when you press Run in Circuit?
Circuit uses LLVM Clang distributed through YoWASP to compile C17 source to WebAssembly, then executes the result with WASI support inside a Web Worker. Compilation and execution happen on your device. The terminal connects the running program to supported standard input and output.
Clang translates the C source; the browser’s WebAssembly execution engine handles the compiled module; the WASI support supplies facilities such as stdin and stdout. An engine may compile WebAssembly into native instructions internally. A Run button can orchestrate these stages without displaying a separate command for each one.
Answering scanf is runtime input to the already-running C program. Your reply is data, not a new C source line being interpreted. Changing the editor text prepares a future build; it does not change the process that is currently waiting in the terminal.
You can try the C calculation in Circuit and inspect the real output. For native APIs, multiple source files, or a desktop debugger, use an appropriate local toolchain. Read the runtime documentation and browser and GCC comparison before expecting two environments to support the same libraries.
Three experiments that make the distinction stick
- Separate source from executable. Build the desktop C example, change its limit to 5, and launch the old result before rebuilding. Predict both outputs. The saved source and the runnable artifact represent different versions until the new build succeeds.
- Separate valid code from correct logic. Reset limit to 4, use the < loop condition, predict 6, then confirm it. Explain why neither a compiler nor an interpreter can infer that you wanted to include the final number.
- Trace the actual pipeline. For Circuit, identify C source, the Clang build, the WebAssembly module, the runtime, and terminal output. For CPython, identify source, bytecode preparation, and execution. Label jobs rather than memorizing a two-column list of languages.
These observations help with ordinary debugging: check that the current source built, that the intended result was launched, and that the executed operations match your requirements. Translation, execution, and testing each answer a different question.