A mental model for understanding Python's role
Every mainstream language fits a mental slot. C is a systems language. JavaScript is a browser language. Rust is a safety-focused systems language. SQL is a query language.
Python doesn't fit. It can be tricky, for an experienced programmer, to grasp what Python actually is and which slot to put it in. Often, they conclude "I don't like Python" and express confusion at its vast popularity in the 2020s.
A slippery language
Traditionally we're taught to classify languages along a few axes:
- compiled vs interpreted
- scripting vs "real" languages
- imperative vs OO vs functional
Python fits poorly into all of them.
It isn't a compiled language in the C or Rust sense: it doesn't result in a standalone executable. But it isn't purely interpreted either, since source code must be processed before execution. It supports imperative, object-oriented and functional styles but isn't optimized for any of them. It began as a scripting language, but today it's used to build large, long-running systems.
So what IS Python?
The Key Insight: Python Is Not Defined by Its Output
The turning point is to realize that Python is not defined by the artefact it produces.
C, C++, Rust, Zig and Fortran produce binaries that can be directly run. The output is the thing. Once compiled, the language largely disappears from the execution model.
Python doesn't work like that.
Python source code is compiled to bytecode and that bytecode is executed by a virtual machine. The VM, the object model, the garbage collector and the standard library are not incidental. They are Python. A Python program needs this ecosystem to run; it can't run standalone, unless all of these components are bundled with it.
Python as a Runtime Ecosystem
In structural terms, Python sits alongside languages with runtimes and ecosystems:
- .NET (C#, F#, VB.NET)
- the JVM (Java, Scala, Kotlin, Clojure)
The similarity is architectural role: in all three cases (.NET, JVM and Python) the runtime is the unit of execution, not the compiled artefact. "Interpreted vs compiled" is therefore a false dichotomy. CPython parses source to an Abstract Syntax Tree, compiles it to bytecode, and executes that bytecode on a VM. That's not conceptually different from Java or C#.
The differences matter too.
- .NET and the JVM have JIT compilation to native code; CPython does not by default.
- .NET and the JVM enforce static typing as part of the compilation model; Python's type hints are advisory.
- .NET and the JVM produce native distributable artefacts (.dll, .jar) with stable ABIs (Application Binary Interfaces); Python does not, making it more difficult to call. Python prefers to call other libraries rather than be called itself.
So Python belongs in the "runtime ecosystem" category, but it's a looser, more dynamic variant; it trades static type-safe guarantees for flexibility and rapid development.
Python's Primary Role: Orchestration
The solution to the puzzle of "What IS Python?" is to realize that Python is a runtime-centric language, whereupon its real role becomes obvious.
In numerical computing, data engineering and machine learning, Python's role becomes clear: it orchestrates work rather than performing it. The most important Python libraries—NumPy, SciPy, Pandas, PyTorch, TensorFlow—are not written in Python in any meaningful sense. Python provides the API, the glue and the control flow. The heavy computation happens in C, C++, Fortran or CUDA libraries that expose a C ABI.
Python performs the same role over its libraries as:
- SQL over databases
- shell over Unix
- VBA over Office
It is an orchestration language sitting above high-performance systems. That's why it thrives in scientific computing, data pipelines and machine learning. You build rapidly and easily, with simple syntax, while the underlying libraries deliver the performance. So long as orchestration overhead is low, Python-based systems can scale surprisingly far.
But That's Not the Whole Story
The orchestration model explains Python's dominance in scientific and data-heavy domains — but it does not explain everything.
In web development and business applications—Django, Flask, FastAPI—Python is doing the work directly: handling HTTP requests, processing strings, executing business logic. Here, Python trades raw performance for development speed and ecosystem breadth. A Django application will be slower than an equivalent in Go or C#, but it may ship months earlier.
The orchestration framing applies beautifully well to numerical/ML workloads. For web and business logic, the framing is different: Python is a productive general-purpose language that prioritizes developer time over CPU time.
Why Python Succeeds
Python's popularity isn't mysterious once you accept this trade-off.
Being able to assemble things quickly, in readable code, with vast ecosystem support, matters more for mass adoption than type-safety, compilation speed or raw performance. Make something easy, and more people will do it; make something quick to do, and more people will do it, more often.
Python lowers the barrier to entry for proof-of-concept and prototype work. You can validate an idea in hours rather than days. If performance becomes critical later, you translate hot paths into a compiled language—but you've already learned what needs building.
Getting something working at all, quickly, turns out to be more important than getting it working fast or elegantly. Shell scripting demonstrated this in the 1970s; Visual Basic and VBA did this in the 1990s; Python demonstrates it today. Make it easy and fast to build, and they will come and build.
When Python Is the Wrong Choice
Intellectual honesty requires acknowledging where Python doesn't belong:
- Hard real-time systems — Garbage collection pauses are unacceptable when deadlines are measured in microseconds.
- Mobile applications — Neither iOS nor Android use Python as a first-class development language.
- Browser code — JavaScript and WebAssembly own this space.
- Memory-constrained embedded systems — Python's runtime overhead is prohibitive on microcontrollers.
- Latency-critical network services — Where every millisecond matters, Go, Rust or C++ are better choices.
- CPU-bound pure-Python workloads — If you can't offload to native libraries, Python's interpreter speed becomes a genuine bottleneck.
Python excels at orchestration, rapid prototyping and domains with strong library support. It is not a universal solution.
Why Python still feels slippery
Even with this framing, Python can still feel oddly unsatisfying if you come from strongly structured languages.
Compared with .NET or the JVM, Python has:
- weak static guarantees
- loose module boundaries
- a simpler, leakier object model
If you're used to the discipline of C#, the functional elegance of F# or the precision of Rust, Python can feel vague. Things work — until they don't — and the language often declines to help you reason about correctness ahead of time.
That's a real cost. But as the previous section argues, for many problem domains it's a cost worth paying.
Clearing Up Misconceptions
"Python is slow."
True for CPU-bound pure-Python code. False when Python orchestrates native libraries—NumPy array operations execute at C speed regardless of Python's overhead.
"Python is a scripting language."
Historically accurate; Python originated as a scripting tool. But "scripting language" now undersells what Python has become.
"Python is interpreted."
Technically incorrect. CPython compiles source to bytecode, then executes that bytecode on a VM. The distinction matters when reasoning about performance and behaviour.
A Better Language Taxonomy
A clearer classification:
| Category | Examples | Defining Trait |
|---|---|---|
| Standalone native | C, C++, Rust, Zig, Go, Fortran | The binary is the product |
| Runtime ecosystems | Python, JVM languages, .NET languages | The runtime is the product |
| Host-bound scripting | Bash, PowerShell, VBA, Lua | The host environment is the product |
Python belongs firmly in the second group—with the caveat that it's a more dynamic, less rigidly structured member than .NET or the JVM.
A brief note for Rust and Go proponents
A common challenge: Python's role is better served by "doing it properly" in a compiled language from the start.
That view makes sense if your problem is well-specified, stable, performance-critical and worth committing to upfront architectural constraints. In such cases, Rust or Go can be excellent choices.
But many real-world problems do not start that way. They begin as ill-defined, exploratory or fast-moving systems: data pipelines, research code, internal tools, integration glue. A research team needs to test an idea quickly at small scale. A business team needs a tactical solution because the problem won't wait for strategic architecture.
In those contexts, early commitment to strict typing, memory models or concurrency primitives can slow learning rather than accelerate it.
Python and compiled languages are not competitors but complements: Python for orchestration and discovery; Rust, Go or C# for stabilised, performance-critical components. Your Python prototype becomes your teacher—clarifying what the real system needs to do.
Summary
Python isn't confused, incoherent or a "toy" language. It simply departs from the mental models of earlier language generations.
Python is a runtime-centric ecosystem that excels at orchestration, rapid prototyping and leveraging high-performance native libraries. It trades static guarantees and raw speed for flexibility, readability and development velocity.
That trade-off turns out to be exactly what a large portion of programmers needs. And that makes it incredibly useful — and wildly popular.
Top comments (0)