Programming languages
What actually separates one language from another in practice: how the code runs, when types are checked, who frees memory, and which styles of program and concurrency it makes easy.
15 technologies on this page ↓How it works
Syntax is the visible difference, but a few deeper choices decide how a language behaves in production. The first is how the code runs. C, C++, Rust, Go, Zig and Haskell (with GHC) are compiled ahead of time to machine code for one operating system and CPU. Java, Scala and Clojure compile to bytecode for the JVM, C# and F# to intermediate language for .NET, Erlang and Elixir to bytecode for BEAM; a runtime virtual machine then compiles the busy parts to machine code as the program runs. Python’s main implementation, CPython, compiles source to bytecode too, but runs it in an interpreter loop. The second is typing. A statically typed language (Java, C#, Go, Rust, Haskell) checks types at compile time, and many infer them, so let x = 5; in Rust needs no annotation. A dynamically typed language (Python, Clojure, Erlang, Elixir, Racket) attaches types to values and checks them as each line runs. Strong and weak are looser words for how willingly a language converts between types on its own: JavaScript turns "1" + 1 into "11", Python raises a TypeError. The third is memory. In C and Zig the programmer frees what they allocate. Java, Go, Python and most others use a garbage collector. Rust checks ownership rules at compile time and frees each value when its owner goes out of scope, with no collector. The rest is style and concurrency: which paradigms the language favours (object-oriented, functional) and how it runs many things at once (OS threads, async/await, goroutines, lightweight processes).
Ways of getting dinner from a recipe. A compiled language is a meal cooked ahead and frozen: quick to serve, but only in kitchens with the right oven. A language on a virtual machine ships a meal kit that any kitchen with the standard equipment finishes on site, and the cook gets faster at the dishes ordered most. An interpreted language sends the cook along to read the recipe out, step by step, every time.
Reach for it when
- Choosing a language for a new service: match its runtime, typing and memory model to the job, not just its syntax
- Explaining why the same algorithm is fast in one language and slow in another, or why one crashes on a type error only in production
- Picking between a garbage collector (simpler code, some pauses) and manual or ownership-based memory (predictable, more work)
- Mixing languages: languages that share a runtime (JVM, .NET, BEAM) can call each other’s libraries directly
Think twice when
- Ranking languages as fast or slow in general: the implementation, the libraries and the workload matter more than the language’s name
- Treating “compiled” and “interpreted” as fixed labels: CPython compiles to bytecode, and JIT compilers turn VM bytecode into machine code
- Choosing on paradigm alone: most modern languages mix object-oriented and functional features
A real example
A chat server keeps 100,000 connections open, each mostly waiting. In Erlang or Elixir each connection gets its own BEAM process, which starts at 327 words (2,616 bytes on a 64-bit machine), about 262 MB for all of them, and the runtime switches out any process after 4,000 reductions, roughly function calls, so none can hog a core. In Go each connection gets a goroutine whose stack starts at a few kilobytes and grows only when needed. In Java 21 each can get a virtual thread instead of an OS thread. C and Rust have no such scheduler built into the language, so the usual design there is an event loop in which a few OS threads serve every connection. Same job, four concurrency models, each a consequence of how the language and its runtime were designed.
More on programming languages
Narrower ideas that build on this one.
- Static dispatchThe compiler decides which function each call runs, so the program jumps straight to it, or skips the call by inlining it.
- Dynamic dispatchThe function a call runs is chosen at run time from the object it’s called on, usually through a table of function pointers.
- Runtime virtual machinesA program that runs other programs: compilers emit portable bytecode, and the runtime loads it, compiles the busy parts to machine code, collects garbage and schedules work on whatever machine it lands on.
- Foreign function interfacesHow code in one language calls functions compiled from another: both sides meet at the platform’s C calling convention, and glue code converts values and settles who frees what.
Kinds of programming languages
Specific products, and what each does differently.
Compiled to machine code →
A compiler turns the whole program into machine code for one operating system and CPU before it runs, so it starts fast and needs no virtual machine.
Run on a virtual machine →
The compiler emits portable bytecode for a runtime such as the JVM, .NET or BEAM, which loads it, compiles the busy parts to machine code and manages memory and threads.
Interpreted →
An interpreter reads the program and carries it out as it goes, with no separate build step, at the cost of speed in tight loops.
| Compiled to machine code | Open source | ISO/IEC JTC1/SC22/WG14 | ISO standard (ISO/IEC 9899:2024); compilers carry their own licences | 2026-10-08 | |
| Compiled to machine code | Open source | ISO/IEC JTC1/SC22/WG21 | ISO standard (ISO/IEC 14882:2024); compilers carry their own licences | 2026-10-08 | |
| Compiled to machine code | Open source | Google (the Go team) | BSD 3-Clause | 2026-10-08 | |
| Compiled to machine code | Open source | GHC developers and the Haskell community | BSD-3-Clause (GHC) | 2026-10-08 | |
| Compiled to machine code | Open source | Racket team (PLT) | MIT or Apache 2.0, at your option | 2026-10-08 | |
| Compiled to machine code | Open source | Rust Project, supported by the Rust Foundation | MIT or Apache 2.0 (your choice) | 2026-10-08 | |
| Compiled to machine code | Open source | Zig Software Foundation | MIT | 2026-10-08 | |
| Run on a virtual machine | Open source | Microsoft / .NET Foundation | MIT (Roslyn compiler) | 2026-10-08 | |
| Run on a virtual machine | Open source | Rich Hickey / Clojure core team | Eclipse Public License 1.0 | 2026-10-08 | |
| Run on a virtual machine | Open source | The Elixir Team | Apache 2.0 | 2026-10-08 | |
| Run on a virtual machine | Open source | Ericsson (Erlang/OTP team) | Apache 2.0 | 2026-10-08 | |
| Run on a virtual machine | Open source | Microsoft and the F# Software Foundation | MIT | 2026-10-08 | |
| Run on a virtual machine | Open source | Oracle / OpenJDK | GPL 2.0 with the Classpath Exception (OpenJDK) | 2026-10-08 | |
| Run on a virtual machine | Open source | EPFL and Scala Center | Apache 2.0 | 2026-10-08 | |
| Interpreted | Open source | Python Software Foundation | PSF License 2.0 | 2026-10-08 |
Fifteen languages at a glance#
How each one usually runs, checks types and manages memory. “First appeared” is the first public release unless noted.
| Language | First appeared | Created by | How it usually runs | Typing | Memory |
|---|---|---|---|---|---|
| C | 1972 (developed 1969–1973) | Dennis Ritchie, Bell Labs | Compiled to machine code | Static | Manual: malloc and free |
| C++ | 1985 (work began 1979 as “C with Classes”) | Bjarne Stroustrup, Bell Labs | Compiled to machine code | Static | Manual, made automatic by destructors (RAII) |
| Erlang | 1986 (work began); open source 1998 | Joe Armstrong, Robert Virding and Mike Williams, Ericsson | Bytecode on BEAM | Dynamic | Garbage collected, one heap per process |
| Haskell | 1990 (Haskell 1.0 report) | A committee, report edited by Paul Hudak and Philip Wadler | Compiled to machine code (GHC) | Static, inferred | Garbage collected |
| Python | 1991 (work began December 1989) | Guido van Rossum, CWI | Interpreted (CPython runs bytecode) | Dynamic; type hints not enforced | Reference counting plus a cycle collector |
| Racket | 1995 (as PLT Scheme; renamed 2010) | PLT, led by Matthias Felleisen | Compiled to machine code (Racket CS, on Chez Scheme) | Dynamic; Typed Racket adds static types | Garbage collected |
| Java | 1996 (JDK 1.0) | James Gosling, Sun Microsystems | Bytecode on the JVM | Static | Garbage collected |
| C# | 2002 (C# 1.0) | Anders Hejlsberg, Microsoft | Intermediate language on .NET | Static | Garbage collected |
| F# | 2002 (v0.1); F# 2.0 in 2010 | Don Syme, Microsoft Research Cambridge | Intermediate language on .NET | Static, inferred | Garbage collected |
| Scala | 2003 | Martin Odersky, EPFL | Bytecode on the JVM | Static, inferred | Garbage collected (the JVM’s) |
| Clojure | 2007 | Rich Hickey | Bytecode on the JVM | Dynamic | Garbage collected (the JVM’s) |
| Go | 2009 (open source; design began 2007) | Robert Griesemer, Rob Pike and Ken Thompson, Google | Compiled to machine code, runtime linked in | Static | Garbage collected |
| Elixir | 2014 (1.0; first commit 2011) | José Valim | Bytecode on BEAM | Dynamic, with gradual types being added | Garbage collected, one heap per process |
| Rust | 2015 (1.0; begun 2006) | Graydon Hoare; sponsored by Mozilla from 2009 | Compiled to machine code | Static, inferred | Ownership, checked at compile time |
| Zig | 2016 | Andrew Kelley | Compiled to machine code | Static, with compile-time generics | Manual, through allocators passed explicitly |
- “Compiled”, “bytecode” and “interpreted” describe the usual implementation, not the language: ClojureScript compiles Clojure to JavaScript, C# can be compiled ahead of time with Native AOT, and CPython has an experimental JIT.
Typing: static or dynamic, strong or weak, inferred#
Static and dynamic answer when types are checked. Rust is statically typed, so the compiler must know the type of every variable before the program runs, and C# says every variable “has a type known at compile time.” Python checks as it goes: a name can hold an int now and a str later, and annotations are optional, because “the Python runtime does not enforce function and variable type annotations.” Gradual typing sits in between: Elixir is adding set-theoretic types to its compiler, with a dynamic() type for values checked at run time.
Inference removes most of the cost of static types. Haskell, F#, Scala and Rust work out the types of local values from how they’re used, so the code reads almost like a dynamic language while the compiler still checks it.
Strong and weak have no agreed definition. They usually mean how far the language converts values between types on its own. JavaScript’s + converts the other operand to a string if either side is a string, so "1" + 1 is "11". Python refuses the same expression with a TypeError, and Java’s specification calls the language “strongly and statically typed.”
Memory: manual, garbage collected or owned#
In C, memory from malloc stays allocated until the program passes it to free; forgetting leaks it, and freeing the same block twice is undefined behaviour. Zig keeps that model on purpose: the language “performs no memory management on behalf of the programmer,” and every function that allocates takes an allocator as a parameter, which is why Zig needs no runtime and suits kernels and embedded devices. C++ is manual too, but destructors release resources when an object goes out of scope (RAII), which Stroustrup describes as memory management that is “safe and implicit without a garbage collector.”
Most languages hand memory to a garbage collector instead. Java’s specification puts it bluntly: objects “are never explicitly deallocated.” CPython frees most objects as soon as their reference count drops to zero and runs a cycle collector for the rest. BEAM gives every process its own small heap and collects each one separately.
Rust takes a third route. Every value has one owner, the compiler checks the ownership rules, and the value is freed when its owner goes out of scope. There is no collector and, in the Rust book’s words, none of these checks slow the program down while it runs; the cost is learning the rules.
Paradigms and concurrency#
Paradigm is about how a program is organised. Object-oriented languages bundle data with the methods that act on it; functional languages build programs from functions and immutable values. Few languages are pure examples: C# describes itself as object-oriented with features from other paradigms, Scala and F# mix both, and Clojure calls itself a functional language with a dynamic emphasis.
Concurrency is often where languages differ most. Go multiplexes cheap goroutines onto OS threads; Erlang and Elixir run millions of isolated processes that share nothing and exchange copied messages; Java 21 added virtual threads; C# offers async/await over Tasks. The models are compared in Synchronous and asynchronous execution.
In code · python
import dis
def add(a, b):
return a + b
dis.dis(add) # CPython compiled add() to bytecode before running it
# Python 3.14 prints (opcodes change from version to version):
# 3 RESUME 0
# 4 LOAD_FAST_BORROW_LOAD_FAST_BORROW 1 (a, b)
# BINARY_OP 0 (+)
# RETURN_VALUE
print(add(1, 2)) # 3: dynamic typing, + on two ints
print(add("1", "2")) # 12: the same code on two strings
try:
add("1", 2) # strong typing: no silent conversion
except TypeError as e:
print(e) # can only concatenate str (not "int") to strOften confused with
Where it came from1972–2015
For the first decades, the main question was how close to the machine to sit. Dennis Ritchie built C at Bell Labs between 1969 and 1973, with its most creative period in 1972, as a systems language for the young Unix; it grew out of the typeless BCPL and B and gave them a type structure. Bjarne Stroustrup started “C with Classes” down the corridor in 1979, and the first commercial C++ came out in October 1985. Both compiled straight to machine code and left memory to the programmer.
Others traded speed for convenience or safety. Guido van Rossum started Python over the 1989 Christmas holidays at CWI, building on his work on the interpreted language ABC, and posted it to Usenet in February 1991. A committee formed in 1987 to unify more than a dozen lazy functional languages published Haskell 1.0 on 1 April 1990. Java (1996) and C# (2002) made compiling to a portable virtual machine with a garbage collector the default for business software.
The 2000s and 2010s brought languages built for specific pains. At Google, Robert Griesemer, Rob Pike and Ken Thompson started Go on 21 September 2007 because they had to choose between efficient compilation, efficient execution and ease of programming; Go aimed for all three, with garbage collection and concurrency built in. Graydon Hoare began Rust in 2006, Mozilla sponsored it from 2009, and Rust 1.0 arrived in May 2015 with memory safety enforced by the compiler instead of a collector.
Why it’s built this way
- Compile to the machine, or to a portable virtual machine
- Native code starts fast and needs nothing installed, but has to be rebuilt for every platform. Java targeted networked devices where code had to run on any client, so it compiled to bytecode and let the runtime finish the job.
- Check types before the program runs, or while it runs
- Static checking catches mistakes early and lets the compiler optimise; dynamic typing is quicker to write and change. Go’s designers set out to combine the ease of an interpreted, dynamically typed language with the efficiency and safety of a statically typed, compiled one.
- Collect garbage, or prove memory safety at compile time
- Garbage collection removed whole classes of memory bugs but cost run time and memory. Rust’s ownership rules give the same safety with checks done entirely by the compiler, at the price of a steeper learning curve.
- Keep the language small and leave memory to the programmer
- Zig has no runtime and no hidden allocations, so the same code works in kernels, embedded devices and real-time software where a collector or hidden control flow isn’t acceptable.
Timeline
- 1972
C’s most creative year at Bell Labs, as the language of Unix
- 1979
Bjarne Stroustrup starts “C with Classes”, later C++
- 1990
Haskell 1.0 report published on 1 April
- 1991
Guido van Rossum posts Python to Usenet in February
- 1996
JDK 1.0 ships Java on a virtual machine
- 2009
Go becomes open source on 10 November
- 2015
Rust 1.0 released on 15 May
- Dennis Ritchie: The Development of the C Language (1993)
- Bjarne Stroustrup’s FAQ: When was C++ invented?
- Python docs: General Python FAQ (Christmas 1989; posted to Usenet February 1991)
- Haskell 2010 Report: Preface (the 1987 FPCA meeting and the committee)
- Hudak, Hughes, Peyton Jones and Wadler: A History of Haskell: Being Lazy With Class (HOPL III, 2007)
- The Java Virtual Machine Specification, Java SE 21: Chapter 1, A Bit of History
- Wikipedia: Java version history (JDK 1.0 released 23 January 1996)
- Microsoft Learn: The history of C# (C# 1.0, January 2002)
- Go FAQ: What is the history of the project? and What were the guiding principles?
- MIT Technology Review: How Rust went from a side project to the world’s most-loved programming language (Feb 2023)
- Rust Blog: Announcing Rust 1.0 (15 May 2015)
- Zig language overview (no hidden control flow or allocations; manual memory management)
Sources52
- Dennis Ritchie: The Development of the C Language (1993)
- cppreference: malloc (memory must be released with free)
- cppreference: free (undefined behaviour if the memory was already freed)
- cppreference: Implicit conversions (C)
- Bjarne Stroustrup’s FAQ (invention 1979–1985; garbage collection; RAII)
- Joe Armstrong: A History of Erlang (HOPL III, 2007)
- Erlang/OTP blog: A Brief History of the BEAM Compiler (Armstrong, Virding and Williams built JAM)
- Erlang/OTP docs: Efficiency Guide, Processes (327 words per new process)
- Erlang/OTP docs: Erlang Garbage Collector (per-process collector)
- Erlang/OTP docs: erlang:bump_reductions/1 (context switch after 4,000 reductions)
- Haskell 2010 Report: Chapter 1 (static polymorphic typing)
- Hudak, Hughes, Peyton Jones and Wadler: A History of Haskell: Being Lazy With Class (Haskell 1.0 report, 1 April 1990, edited by Hudak and Wadler)
- GHC User’s Guide: Options related to code generation
- GHC User’s Guide: Running a compiled program (garbage collector options)
- Python docs: General Python FAQ (history)
- Python docs: History and License (created at CWI by Guido van Rossum)
- Python docs: dis (CPython bytecode)
- Python docs: gc (the collector supplements reference counting)
- Python/C API: Reference counts (an object is deallocated when its count becomes zero)
- Python docs: typing (the runtime does not enforce annotations)
- Python docs: What’s New in Python 3.13 (experimental JIT)
- Racket blog: Racket (formerly PLT Scheme; type safety, garbage collection; June 2010)
- The Racket Reference: 1.5 Implementations (Racket CS and BC)
- Wikipedia: Racket (first appeared 1995; PLT founded by Matthias Felleisen)
- The Java Language Specification, Java SE 21: Chapter 1 (strongly and statically typed; garbage collector)
- The Java Virtual Machine Specification, Java SE 21: Chapter 2 (objects are never explicitly deallocated)
- Wikipedia: Java (programming language) (designed by James Gosling)
- Wikipedia: Java version history (JDK 1.0, 23 January 1996)
- OpenJDK: JEP 444, Virtual Threads (Java 21)
- Microsoft Learn: A tour of C# (strongly typed; automatic memory management; object-oriented with other paradigms)
- Microsoft Learn: The history of C# (C# 1.0, January 2002)
- Wikipedia: C Sharp (designed by Anders Hejlsberg)
- Don Syme: The Early History of F# (HOPL IV, 2020; v0.1 on 4 June 2002, F# 2.0)
- Microsoft Learn: What is F# (type inference)
- Martin Odersky: Scala’s Prehistory (designed from 2001 at EPFL; first public release 2003)
- Scala docs: Tour of Scala (statically typed; type inference)
- Scala docs: Scala for Java Programmers (standard Java class files)
- Clojure: Rationale (a functional language with a dynamic emphasis)
- Clojure: Hosted on the JVM (shares the JVM’s type system and GC)
- Wikipedia: Clojure (released publicly in October 2007)
- Go FAQ (history, statically typed, garbage collection, goroutine stacks, statically linked runtime)
- Elixir blog: Elixir v1.0 released (18 September 2014; first commit 9 January 2011)
- Elixir docs: Gradual set-theoretic types
- Elixir docs: Code (modules compiled to bytecode in .beam files)
- Rust Blog: Announcing Rust 1.0 (15 May 2015)
- MIT Technology Review: How Rust went from a side project to the world’s most-loved programming language (Feb 2023)
- The Rust Programming Language: Data Types (statically typed; the compiler infers types)
- The Rust Programming Language: What Is Ownership?
- Andrew Kelley: Introduction to the Zig Programming Language (8 February 2016)
- Zig language overview (manual memory management; allocators)
- Zig language reference 0.15.2 (comptime generics; Memory)
- MDN: Addition (+) (string concatenation converts the other operand)