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).

Bold names are still significantly used today (roughly 5%+ of developers in the2025 Stack Overflow Developer Survey).

Highlight shared traits

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.

CRustGoPython
Year1972201520091991
ParadigmProceduralMulti-paradigmProceduralMulti-paradigm
Type systemStatic 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
ConcurrencyC11 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.

25

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
7

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
3

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
23

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
15

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
9

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
16

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
10

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
10

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
24

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
14

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
7

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
10

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
3

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
7

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
9

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
3

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
7

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
14

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
11

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
7

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
7

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