Quick Answer

Compiled and interpreted describe implementations, not languages. A C++ compiler translates your whole program to machine code before it runs, so types are fixed and the CPU executes native instructions directly. CPython compiles your source to bytecode and then runs that bytecode in a loop, deciding what every operation means at runtime. JIT engines like the JVM and V8 sit in between: they interpret first, then compile the hot paths to machine code while the program is running.

The question is about implementations, not languages

Textbooks put C and C++ in a compiled column and Python and JavaScript in an interpreted one, and the table is wrong in a way that matters once you start reasoning about performance.

Nothing in the C++ standard says the language must be compiled ahead of time, and interpreters for it exist. Nothing in Python's specification says it must be interpreted: PyPy runs Python with a just-in-time compiler, and Cython compiles Python-like source to C. The property belongs to the implementation you happen to be running, not to the language you wrote.

The clearest evidence is sitting in your own project folder. Run a Python program that imports a module and a __pycache__ directory appears containing a .pyc file. That is compiled output. CPython parses your source, builds a syntax tree, and compiles it to bytecode, a compact instruction set for a virtual machine it defines. It caches that bytecode and reuses it when the source has not changed. So Python does compile. It simply does not compile to instructions your CPU can execute.

What people actually mean by the distinction is a pair of questions. When does translation happen, before the run or during it? And what is the target, real machine code or an instruction set for a virtual machine that then has to be interpreted? Ask it that way and the differences that follow, in speed, in error timing and in what you ship to a server, stop being trivia and start being predictable.

What a C++ compiler does before your program runs

Building a C++ program runs several stages. The preprocessor expands includes and macros. The compiler parses the result, checks types, and emits assembly. The assembler turns that into an object file of machine code, and the linker stitches the object files and libraries into one executable.

The decisive part is that the compiler knows the types of everything.

int add(int a, int b) {
    return a + b;
}

Both operands are int, always, so the compiler selects one integer add instruction, decides which registers hold the values, and is done. Nothing is checked at runtime because there is nothing left to check. From there it can inline small functions so a call disappears, keep loop counters in registers instead of memory, delete code whose result is never used, unroll loops, and lay a struct out so that a whole object fits in a cache line. You can see the result with g++ -O2 -S main.cpp, or paste the function into Compiler Explorer and watch the assembly change as you change the optimisation flag.

The costs are real too. You wait for a build, and the wait grows with the project. Change a header and everything including it recompiles. The output is a binary for one operating system and one architecture, so shipping to Linux and Windows means building twice. And because there is no runtime watching, mistakes that another language would report become undefined behaviour: reading past the end of an array does not raise anything, it just reads whatever bytes are there.

That is the bargain. You give up flexibility and build time, and you get instructions the processor runs with nothing in the way.

What Python does while your program runs

Now the same function in Python:

def add(a, b):
    return a + b

The interpreter cannot choose an add instruction here, because it cannot know what a and b are. They might be integers, floats, strings, lists, NumPy arrays, or an object of your own class that defines __add__. All of those are legal, and which one arrives can differ between two calls in the same loop.

So the work has to happen on every execution: look at the object, find its type, look up the add operation for that type, invoke it, and allocate an object to hold the result. You can see the shape of it directly:

import dis

def add(a, b):
    return a + b

dis.dis(add)

The output shows a handful of bytecode instructions: two loads of the arguments, a binary add, and a return. The exact opcode names have changed between Python versions, so do not memorise them. What matters is that each one is a case in a big dispatch loop written in C, and that the add opcode does the type lookup described above rather than being an add.

There is a second cost that is easy to miss. Every Python integer is a heap object carrying a reference count and a type pointer. A list of a million integers is a million objects plus an array of pointers to them, scattered across memory, where the C++ equivalent is four megabytes of contiguous integers. Summing the C++ version streams through cache; summing the Python one chases pointers.

What you buy with all of that is genuine. The same add works for anything addable. There is no build step, so the edit-run cycle is instant. You get a REPL, and you can inspect and change objects, including classes, while the program is running.

JIT compilation: the middle ground

Java looks compiled and behaves like neither. javac compiles your source to .class files of JVM bytecode, and the JVM starts by interpreting that bytecode while counting how often each method and loop runs. Once something crosses a threshold, the just-in-time compiler translates that method into real machine code and future calls go straight to the compiled version. V8 does the same for JavaScript in Chrome and Node, and PyPy does it for Python.

A JIT can do something an ahead-of-time compiler cannot: optimise using facts that are only true at runtime. If a call site has only ever seen one class, it can compile a direct call and inline the body, even though the language allows any subclass. If a JavaScript variable has always held an integer, it can generate integer arithmetic instead of generic number handling. These are speculative, so the compiler installs a cheap guard, and if the assumption ever breaks the code deoptimises back to the interpreter and recompiles. It also knows the exact processor it is running on, which an ahead-of-time build targeting a whole family cannot assume.

The consequences show up in operations. There is a warm-up period, which is why a freshly deployed JVM service is slower for its first requests and why autoscaling a new instance under load can be uncomfortable. There is memory overhead for compiled code and profiling data. And it makes naive benchmarking meaningless: timing a single run measures interpretation and compilation, not the steady state, so any honest measurement runs a warm-up loop first and then measures.

What this means when you are writing code

Turn all of it into decisions you can actually use.

When errors appear. A C++ or Java compiler catches type mismatches, misspelled members and missing cases before anything runs. In Python a typo on a rarely taken branch waits until that branch executes, possibly in production. That is not an argument against Python; it is an argument for tests and type hints, and it is why linting and mypy carry weight in Python projects that C++ gets from the compiler for free.

When speed actually matters. If the core of the work is a tight loop running a hundred million iterations of arithmetic, a compiled language wins by a wide margin, and no amount of clever Python closes it. If the program spends its time waiting on a database, an HTTP call or a disk, the language contributes almost nothing to the total and you should optimise for how quickly you can write and change the code.

Python's escape hatch. Saying Python is slow really means the Python-level loop is slow. Push the loop down into compiled code and the gap largely disappears, which is exactly what NumPy, pandas and PyTorch are for:

import numpy as np

data = list(range(1_000_000))

total = 0
for x in data:                    # per-element interpreter dispatch
    total += x

total = np.asarray(data).sum()    # the same loop, inside compiled C

Shipping. A compiled binary needs no runtime installed but must be built per platform. A Python or Node service ships as source plus a matching interpreter version, which is one of the reasons containers became standard practice.

If an interviewer asks why C++ is faster than Python, the answer they want is mechanism, not adjectives: types are resolved at compile time so there is no per-operation dispatch, values are stored directly instead of as heap objects with reference counts, memory is contiguous so the cache works, and the optimiser can inline and use registers. Say that and the question is finished.

Frequently Asked Questions

Is Python compiled or interpreted? Both, in sequence. CPython compiles your source into bytecode, caches it in __pycache__ as .pyc files, and then interprets that bytecode in a loop. So there is a real compile step, but the target is a virtual machine rather than your CPU. Other implementations differ: PyPy adds a just-in-time compiler, and Cython compiles Python-like code down to C.
What exactly is bytecode? A compact instruction set for a virtual machine rather than a physical processor. Python bytecode and JVM bytecode are both examples. It is faster to execute than re-parsing source every time and it is portable, since the same .class or .pyc runs anywhere the corresponding virtual machine exists. The cost is that something still has to translate each instruction into real work while the program runs.
How does a JIT compiler make a program faster over time? It starts by interpreting while recording which methods and loops run most often. Once a piece of code is clearly hot, it compiles that piece into machine code using facts observed at runtime, such as a call site only ever seeing one class. Those assumptions are guarded, so if one is later violated the runtime falls back to the interpreter and recompiles. This is why long-running services get faster after warm-up.
Why is C++ faster than Python for the same algorithm? Mostly because of what happens per operation. In C++ the compiler already knows both operands are integers, so it emits one add instruction. In Python each addition inspects the objects, finds the right operation for their types, calls it and allocates an object for the result. On top of that, C++ stores a million integers contiguously while Python stores a million separate heap objects, so cache behaviour differs sharply too.
Should I learn a compiled language for placements? Most Indian companies accept C++, Java and Python in coding rounds, so pick the one you write fastest and know the standard library of. C++ has an advantage when a problem has tight time limits, because the same correct algorithm can pass in C++ and time out in Python. Whichever you choose, be able to explain the compilation model, because that is a common interview follow-up.