Programming Languages Timeline
What is it?
A big interactive map of programming languages, sorted by paradigm and year they first appeared. Click any language to explore its type system, runtimes, compilers, concurrency model, and a code snippet. You can highlight shared traits to see which ones have garbage collection, monads, async/await, etc., and compare up to four languages side by side. I got the idea from enzet's symbolic-execution timeline (a gorgeous hand-drawn diagram of symbolic execution, SAT/SMT solvers, and fuzzing tools) and thought: why not do the same for programming languages?
Bands run from imperative at the bottom to declarative at the top. Note these are two different axes: "imperative vs. declarative" is about how you instruct the machine, while procedural, object-oriented and functional are about how you structure code. So imperative splits into procedural and object-oriented, while functional sits on the declarative side. Each language sits in the family it was born into (a few are multi-paradigm today).
- Procedural: Explicit step-by-step instructions that read and mutate state, grouped into reusable procedures rather than objects. (C, Fortran)
- Object-oriented: Data and the methods that operate on it are bundled into objects that talk via method calls, usually with inheritance and polymorphism. (Java, Smalltalk)
- Multi-paradigm: Deliberately supports several styles at once, so a single program can mix procedural, object-oriented and functional code. (Python, Rust, Go)
- Functional (impure): Programs are built by composing and passing functions, but side effects like I/O and mutation are allowed anywhere. (Lisp, OCaml)
- Functional (pure): Functions have no side effects and always return the same output for the same input; effects are isolated by the type system. (Haskell)
- Logic / relational: You state facts and rules and the engine searches for values that satisfy them, instead of you writing the control flow. (Prolog)
- Dependently typed: Types can depend on values, so a type can express a full specification and type-checking the program becomes a proof it is correct. (Coq, Lean)
Bold names are still significantly used today (roughly 5%+ of developers in the2025 Stack Overflow Developer Survey).
Toggle one or more traits to see which languages on the timeline share the same characteristic.
// diff <lang1> <lang2>
Compare languages
Pick up to four languages and see how they differ: type system, runtime, concurrency model, and which traits they share. The default comparison is C vs. Rust vs. Go vs. Python, four very different approaches to systems and application programming.
| C | Rust | Go | Python | |
|---|---|---|---|---|
| Year | 1972 | 2015 | 2009 | 1991 |
| Paradigm | Procedural | Multi-paradigm | Procedural | Multi-paradigm |
| Type system | Static and manifest, with many implicit arithmetic conversions and explicit pointer casts. C provides limited memory safety, and casts do not override rules such as alignment, object lifetime, and effective type. | Static, strong, inferred, with affine types. Ownership and borrowing let the compiler enforce memory and thread safety in safe Rust without a garbage collector; unsafe code can opt out of some checks. | Static, strong, mostly inferred. Deliberately small: structural interfaces instead of inheritance, and (since Go 1.18) parametric generics with type constraints. | Dynamic, strong, duck-typed. Types are checked at runtime, but optional type hints (PEP 484) let external tools like mypy or Pyright check code statically without changing execution. |
| Runtimes | Native Native (GCC / Clang / MSVC)WebAssembly WebAssembly (Emscripten / clang --target=wasm32)Native TCC -run | Native Native (rustc + LLVM)Native Native (rustc + Cranelift)WebAssembly WebAssembly (wasm32 targets)Interpreter Miri | Native Native (gc toolchain)Native Native (gccgo)WebAssembly WebAssembly (js/wasm, wasip1)Interpreter yaegi | Bytecode VM CPythonJIT PyPyNative Cython / NuitkaBytecode VM MicroPython |
| Compiler / interpreter | GCCClangMSVCTCCICX | rustcrustc + Craneliftgccrsmrustc | gcgccgoTinyGo | CPythonPyPyCythonNuitkamypyc |
| Async executors | · | Tokioasync-stdsmolEmbassyGlommioCompioMonoio | · | asynciouvloopTrioAnyIO |
| Concurrency | C11 defines a concurrency memory model, atomics, and an optional <threads.h> API. Programs also commonly use platform APIs such as POSIX threads; shared-state synchronisation remains explicit. | "Fearless concurrency": ownership and the Send/Sync traits prevent data races in safe Rust at compile time. OS threads are available via std::thread, plus async/await with pluggable executors (Tokio, smol, Embassy, etc.). | Concurrency is a core feature: goroutines are lightweight tasks multiplexed by the runtime onto OS threads, and channels support CSP-inspired communication between them. | In standard GIL-enabled CPython builds, one thread executes Python bytecode at a time, though threads can overlap I/O; multiprocessing is commonly used for CPU parallelism. asyncio provides cooperative concurrency, usually on one event-loop thread. |
| Shared traits | ||||
| Garbage collected | · | · | ✓ | ✓ |
| Manual memory | ✓ | · | · | · |
| Ownership & affine types | · | ✓ | · | · |
| Statically typed | ✓ | ✓ | ✓ | · |
| Dynamically typed | · | · | · | ✓ |
| Null safety | · | ✓ | · | · |
| Parametric polymorphism | · | ✓ | ✓ | · |
| First-class functions | · | ✓ | ✓ | ✓ |
| ADTs & pattern matching | · | ✓ | · | · |
| Immutable by default | · | ✓ | · | · |
| Async / await | · | ✓ | · | ✓ |
| Lightweight concurrency | · | · | ✓ | · |
| Compiled to native | ✓ | ✓ | ✓ | · |
| Interpreted / scripting | · | · | · | ✓ |
Hello, World!
The simplest possible program in each language
C
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}Rust
fn main() {
println!("Hello, World!");
}Go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}Python
print("Hello, World!")Type system
Same task: a generic max(a, b) function
C
#include <stdio.h>
#define max(a, b) ((a) > (b) ? (a) : (b))
int main(void) {
printf("%d\n", max(3, 7));
printf("%f\n", max(1.5, 2.8));
return 0;
}Rust
fn max<T: PartialOrd>(a: T, b: T) -> T {
if a >= b { a } else { b }
}
fn main() {
println!("{}", max(3, 7));
println!("{}", max(1.5, 2.8));
}Go
package main
import "fmt"
type Ordered interface{ ~int | ~float64 | ~string }
func Max[T Ordered](a, b T) T {
if a >= b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 7))
fmt.Println(Max(1.5, 2.8))
}Python
from typing import TypeVar
T = TypeVar("T", int, float, str)
def max_of(a: T, b: T) -> T:
return a if a >= b else b
print(max_of(3, 7))
print(max_of(1.5, 2.8))Concurrency
Same task: spawn 4 workers, each computes i * i, collect results
C
#include <pthread.h>
#include <stdio.h>
long indices[4];
long results[4];
void *square(void *arg) {
long i = *(long *)arg;
results[i] = i * i;
return NULL;
}
int main(void) {
pthread_t t[4];
for (long i = 0; i < 4; i++) {
indices[i] = i;
pthread_create(&t[i], NULL, square, &indices[i]);
}
for (int i = 0; i < 4; i++)
pthread_join(t[i], NULL);
for (int i = 0; i < 4; i++)
printf("%ld ", results[i]);
return 0;
}Rust
use std::thread;
fn main() {
let handles: Vec<_> = (0..4)
.map(|i| thread::spawn(move || i * i))
.collect();
for h in handles {
print!("{} ", h.join().unwrap());
}
}Go
package main
import "fmt"
func main() {
ch := make(chan int, 4)
for i := 0; i < 4; i++ {
go func(n int) { ch <- n * n }(i)
}
for i := 0; i < 4; i++ {
fmt.Printf("%d ", <-ch)
}
}Python
import asyncio
async def square(n: int) -> int:
await asyncio.sleep(0)
return n * n
async def main():
results = await asyncio.gather(*(square(i) for i in range(4)))
print(*results)
asyncio.run(main())// birds of a feather
Shared traits
The same idea keeps reappearing across otherwise very different languages. Each circle collects the languages that share a characteristic; most languages belong to several circles at once. Widely-used languages (per the 2025 Stack Overflow survey) are shown in bold.
Garbage collected
The runtime automatically reclaims memory you are no longer using, so you never have to call free() or worry about memory leaks. The trade-off is occasional pauses while the collector runs.
- Java
- C#
- Kotlin
- Scala
- Clojure
- Groovy
- Go
- Python
- JavaScript
- TypeScript
- Ruby
- PHP
- Lua
- R
- Haskell
- OCaml
- F#
- Erlang
- Elixir
- Gleam
- Lisp
- Scheme
- Racket
- Smalltalk
- Julia
Manual memory
You allocate and free memory yourself (malloc/free, new/delete). This gives maximum control and predictable performance, but one mistake can cause crashes, leaks, or security vulnerabilities.
- C
- C++
- Fortran
- Pascal
- Ada
- Zig
- Forth
Ownership & affine types
The compiler tracks who 'owns' each piece of data and ensures it is freed exactly once. You get memory safety without a garbage collector, at the cost of stricter rules about how you pass data around.
- Rust
- Move
- Cairo 1
Statically typed
Every variable has a type known at compile time. The compiler catches type errors before your program ever runs, which helps prevent bugs in large codebases.
- C
- C++
- C#
- Java
- Kotlin
- Scala
- Go
- Rust
- Swift
- TypeScript
- Haskell
- OCaml
- F#
- Standard ML
- Ada
- Zig
- Nim
- D
- Crystal
- Elm
- PureScript
- Gleam
- Solidity
Dynamically typed
Variables can hold any type, and type errors only appear when the code actually executes. This makes prototyping fast and code concise, but bugs can hide until a specific code path is hit at runtime.
- Python
- JavaScript
- Ruby
- PHP
- Lua
- R
- Perl
- Lisp
- Common Lisp
- Scheme
- Racket
- Clojure
- Erlang
- Elixir
- Smalltalk
Null safety
The type system prevents null-pointer errors at compile time, usually via Option/Maybe types. Instead of crashing with 'null reference', the compiler forces you to handle the 'no value' case explicitly.
- Rust
- Kotlin
- Swift
- TypeScript
- F#
- OCaml
- Haskell
- Elm
- Gleam
Parametric polymorphism
Write a single function or data structure that works with many types (like List<T>). The compiler generates specialised code for each type you use, without you copying and pasting.
- C++
- Java
- C#
- Kotlin
- Scala
- Go
- Rust
- Swift
- TypeScript
- Haskell
- OCaml
- F#
- Standard ML
- Ada
- D
- Nim
Hindley-Milner inference
The compiler figures out types automatically, without you writing annotations, using an algorithm from the ML family. You get the safety of static types with the feel of a dynamically typed language.
- ML
- Standard ML
- Caml
- OCaml
- F#
- Haskell
- Elm
- PureScript
- Miranda
- Clean
Dependent types
Types can depend on values: for example, 'a list of exactly 5 integers' or 'a sorted array'. This lets you express program properties as types and mathematically prove they hold.
- Coq
- Lean
- Lean 4
- Agda
- Idris
- F*
- Twelf
- Isabelle
- Dafny
- Rocq
First-class functions
Functions are values: you can store them in variables, pass them as arguments, and return them from other functions. This is the foundation of functional programming and enables patterns like map/filter/reduce.
- JavaScript
- TypeScript
- Python
- Ruby
- Lua
- R
- Perl
- Swift
- Kotlin
- Scala
- Go
- Rust
- C#
- Haskell
- OCaml
- F#
- Elixir
- Erlang
- Clojure
- Lisp
- Scheme
- Racket
- Smalltalk
- Julia
ADTs & pattern matching
Data is modelled as tagged unions (sum types) and records (product types). Pattern matching lets you destructure values by shape, like a powerful switch/case that the compiler checks for completeness.
- Haskell
- OCaml
- F#
- Standard ML
- Elm
- PureScript
- Rust
- Scala
- Swift
- Elixir
- Erlang
- Gleam
- Miranda
- Clean
Monads / typed effects
Side effects (I/O, errors, state) are wrapped in special types that the compiler tracks. This makes it explicit where effects happen, so pure functions stay pure and bugs from hidden side effects are eliminated.
- Haskell
- PureScript
- F#
- Scala
- Idris
- Elm
- Clean
Immutable by default
Values cannot be changed after creation. To 'update' something you create a new copy. This eliminates a whole class of bugs around shared mutable state and makes concurrent code much safer.
- Haskell
- Elm
- PureScript
- Clojure
- Erlang
- Elixir
- Gleam
- Rust
- OCaml
- Clean
Lazy evaluation
Expressions are not computed until their result is actually needed. This lets you work with infinite data structures (like an infinite list of primes) and skip unnecessary work, though it can make performance harder to predict.
- Haskell
- Miranda
- Clean
Homoiconic macros
Code and data share the same structure (usually nested lists). Programs can inspect and rewrite their own source code at compile time using macros, enabling powerful metaprogramming that other languages cannot express.
- Lisp
- Common Lisp
- Scheme
- Racket
- Clojure
- Elixir
- Julia
Async / await
The language has explicit async/await syntax: functions are marked async, and await suspends them at I/O points so other tasks can run. The scheduler may be single-threaded (JS, Python) or multi-threaded (Rust Tokio, C# ThreadPool, Kotlin coroutines). Go is NOT here because goroutines already make all code implicitly non-blocking: you write synchronous-looking code and the runtime handles scheduling, so async/await syntax is unnecessary.
- JavaScript
- TypeScript
- Python
- C#
- Rust
- Kotlin
- Swift
- F#
- Dart
Actor model: message passing
Each actor (process) has its own private heap and no shared memory at all; the only way to interact is to send an asynchronous message to another actor's mailbox. This removes data races and locks entirely and underpins 'let it crash' fault-tolerant supervision.
- Erlang
- Elixir
- Gleam
Lightweight concurrency
The runtime multiplexes many user-space tasks (goroutines, BEAM processes, virtual threads) onto a small pool of OS threads. Because each task has a tiny growable stack instead of a fixed ~1 MB OS-thread stack, a process can hold hundreds of thousands or millions of them. This is a property of the scheduler, independent of how tasks coordinate (channels, actors, or shared memory).
- Go
- Erlang
- Elixir
- Gleam
- Haskell
- Java
- Kotlin
Compiled to native
The compiler produces machine code directly, with no VM or interpreter at runtime. This gives maximum performance and small standalone binaries that run anywhere without installing a runtime.
- C
- C++
- Rust
- Go
- Zig
- Swift
- Fortran
- Ada
- Pascal
- Haskell
- OCaml
- Nim
- D
- Crystal
Runs on a managed VM
Code runs on a virtual machine (JVM, BEAM, CLR) that handles memory, security, and portability. You write once and run on any platform that has the VM, and you get features like hot code reloading for free.
- Java
- Kotlin
- Scala
- Groovy
- Clojure
- C#
- F#
- Visual Basic
- Erlang
- Elixir
- Gleam
JIT-compiled
A Just-In-Time compiler translates code to machine instructions while the program runs. It watches which code paths are 'hot' and optimises them aggressively, giving scripting-like convenience with near-native speed.
- Java
- C#
- JavaScript
- TypeScript
- Julia
- Erlang
- Elixir
Interpreted / scripting
No separate compile step: you write a file and run it directly. The interpreter reads and executes your code line by line (some use a bytecode VM internally). Development is fast, but execution is typically slower than compiled languages.
- Python
- Ruby
- PHP
- Lua
- R
- Perl
- sh