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?

Click a trait below to highlight matching languages on the timeline

You can toggle multiple traits at once. Matching languages will be highlighted and connected on the chart above.

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

Bands run from imperative at the bottom to declarative at the top. Keep two axes in mind. "Imperative vs. declarative" is about how you instruct the machine: step-by-step commands that mutate state, versus describing the result you want. The bottom band, Imperative, is the classic step-by-step style organised into procedures (C, Fortran, Zig). Object-oriented is still imperative underneath but bundles state and behaviour into objects, and functional sits on the declarative side. Each language sits in the family it was born into (a few are multi-paradigm today).

Imperative

You tell the machine what to do, step by step, and state is mutated directly. Code is organised into procedures (functions) rather than objects. This is the classic imperative style. Think C, Fortran, Zig.

Object-oriented

Data and the operations on it live together inside objects. Objects communicate via method calls, and behaviour is shared through inheritance or interfaces. Think Java, Smalltalk.

Multi-paradigm

The language supports several styles at once: you can write procedural, object-oriented, or functional code in the same program and pick whichever fits best. Think Python, Rust, Kotlin.

Functional (impure)

Programs are built by composing and passing functions. Side effects like I/O or mutation are allowed anywhere, so you get functional style without strict purity. Think Lisp, OCaml, Elixir.

Functional (pure)

Functions have no side effects and always return the same output for the same input. The type system tracks effects explicitly, making reasoning and testing easier. Think Haskell, Elm.

Logic / relational

You declare facts and rules, then ask questions. The engine searches for values that satisfy the constraints, instead of you writing the control flow. Think Prolog, SQL, Datalog.

Formal / Dependently typed

Types can depend on values, so a type can express a full specification. If the code compiles, it is mathematically proven correct. Think Coq/Rocq, Lean, Agda.

// another axis: how the code is run

Scripting vs dynamic languages

Paradigm is not the only way to slice this map. Two families that often get lumped together are actually different things:

Scripting (glue / automation)

Built to drive other programs and automate tasks: run commands, move files, wire pipelines. A classic shell is read and executed command by command, with no compiler building a program ahead of time. Think Bash / sh, AWK.

Dynamic languages

General-purpose languages whose types are resolved at runtime. The tell-tale sign: the compiler is a library of the runtime. Running the program means the runtime loads the source, compiles it to bytecode or machine code on the fly, and executes it in the same process, so there is no separate build step. Think JavaScript, Python, Ruby.

The line is fuzzy: an embeddable language like Lua is a fully dynamic language (it has a real bytecode compiler bundled in its runtime) that also happens to be a favourite for scripting. Use the Scripting and Interpreted (compiler in the runtime) toggles in Shared traits to light these two groups up on the timeline.

// 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
ParadigmImperativeMulti-paradigmImperativeMulti-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. Free-threaded CPython builds, available since 3.13, can run Python threads in parallel across cores. asyncio provides cooperative concurrency, usually on one event-loop thread.
Shared traits
ADTs & pattern matching···
Async / await··
Compiled to native·
Dynamically typed···
First-class functions·
Garbage collected··
Immutable by default···
Interpreted (compiler in the runtime)···
Lightweight concurrency···
Manual memory···
Null safety···
Ownership & affine types···
Parametric polymorphism··
Statically typed·

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>

static int max_int(int a, int b) { return a > b ? a : b; }
static double max_double(double a, double b) { return a > b ? a : b; }

#define max(a, b) _Generic(((a) + (b)), \
    int: max_int, \
    double: max_double \
)((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.

3

Actor model: message passing

The BEAM ecosystem supports lightweight actors with isolated process state and asynchronous mailboxes, commonly combined with links and supervision. Most ordinary messages are copied, while runtime facilities such as shared binaries or ETS mean isolation is not literally an absence of all shared memory.

  • Erlang
  • Elixir
  • Gleam
30

ADTs & pattern matching

The language provides algebraic or inductive data types, such as tagged unions and product types, together with pattern matching or case analysis. Static systems can often diagnose missing cases, although the strength of exhaustiveness checking varies.

  • Haskell
  • OCaml
  • F#
  • Standard ML
  • Elm
  • PureScript
  • Rust
  • Scala
  • Swift
  • Gleam
  • Miranda
  • Clean
  • ML
  • Caml
  • Idris
  • Agda
  • Lean
  • Lean 4
  • Coq
  • F*
  • Mercury
  • Kotlin
  • TypeScript
  • Zig
  • Rocq
  • Dafny
  • Why3
  • PVS
  • Isabelle
  • HOL
6

Async / await

The language has explicit async/await syntax or equivalent built-in forms: an async computation can suspend at await points while other work proceeds. Execution may use an event loop, thread pool, or pluggable executor, and suspension does not by itself imply parallel execution.

  • JavaScript
  • TypeScript
  • Python
  • C#
  • Rust
  • Swift
32

Compiled to native

A major implementation can compile programs ahead of time to platform-native machine code. The resulting binary may still include or depend on a language runtime, garbage collector, system libraries, and a specific operating-system and processor ABI.

  • C
  • C++
  • Rust
  • Go
  • Zig
  • Swift
  • Fortran
  • Ada
  • Pascal
  • Haskell
  • OCaml
  • Nim
  • D
  • Crystal
  • COBOL
  • PL/I
  • Objective-C
  • Eiffel
  • Delphi
  • Simula 67
  • Mercury
  • Idris
  • Lean 4
  • ALGOL 58
  • ALGOL 60
  • ALGOL 68
  • Common Lisp
  • Standard ML
  • Clean
  • Carbon
  • Mojo
  • Racket
11

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
  • Rocq
  • PVS
  • Automath
  • Nuprl
26

Dynamically typed

Values carry runtime types and many type checks occur as operations execute rather than in a mandatory whole-program compile-time pass. This can support flexible, concise code, while some mismatches remain latent until the relevant path runs.

  • Python
  • JavaScript
  • Ruby
  • PHP
  • Lua
  • R
  • Perl
  • Lisp
  • Common Lisp
  • Scheme
  • Racket
  • Clojure
  • Erlang
  • Elixir
  • Smalltalk
  • Prolog
  • Julia
  • Groovy
  • Wolfram Language
  • APL
  • AWK
  • Self
  • Logo
  • SASL
  • BASIC
  • Oz
56

First-class functions

The language supports callable values (direct functions, closures, delegates, blocks, or functional-interface objects) that can be stored, passed, and returned. The exact representation differs, but it enables higher-order patterns such as map, filter, and 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
  • PHP
  • Java
  • C++
  • Common Lisp
  • Standard ML
  • ML
  • Caml
  • Miranda
  • Clean
  • Groovy
  • Wolfram Language
  • Gleam
  • PureScript
  • Elm
  • Idris
  • Agda
  • Lean
  • Lean 4
  • Coq
  • Rocq
  • F*
  • Mercury
  • Logo
  • Dafny
  • Why3
  • PVS
  • D
  • Nim
  • Crystal
  • Objective-C
  • Self
  • APL
61

Garbage collected

The runtime automatically reclaims at least some unreachable memory, using tracing, reference counting, cycle collection, or a combination. This removes most explicit deallocation, but logical leaks and implementation-dependent collection overhead or pauses are still possible.

  • 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
  • Common Lisp
  • Standard ML
  • ML
  • Caml
  • Miranda
  • Clean
  • Elm
  • PureScript
  • Mercury
  • Prolog
  • Wolfram Language
  • Idris
  • Agda
  • Coq
  • Rocq
  • Lean
  • Lean 4
  • F*
  • D
  • Nim
  • Crystal
  • Visual Basic
  • Self
  • Logo
  • Dafny
  • Eiffel
  • HOL
  • Isabelle
  • PVS
  • Nuprl
  • ACL2
  • LCF
  • Twelf
  • Simula 67
  • Perl
  • Oz
10

Hindley-Milner inference

An ML-family inference system derives many types without annotations and generalises eligible bindings polymorphically. Listed languages may extend or restrict classic Hindley-Milner with features such as type classes, rows, subtyping, or platform interoperability.

  • ML
  • Standard ML
  • Caml
  • OCaml
  • F#
  • Haskell
  • Elm
  • PureScript
  • Miranda
  • Clean
11

Homoiconic macros

Code has a language-level data representation, such as lists, terms, symbolic expressions, or AST objects, that programs and macros can inspect and transform. Quoting, expansion phase, hygiene, and evaluation rules differ between languages.

  • Lisp
  • Common Lisp
  • Scheme
  • Racket
  • Clojure
  • Elixir
  • Julia
  • Logo
  • Prolog
  • R
  • Wolfram Language
23

Immutable by default

Bindings or commonly used data structures are immutable by default, so updates often produce new values. Some listed languages still provide explicit mutable references, arrays, fields, variables, or controlled interior mutation.

  • Haskell
  • Elm
  • PureScript
  • Clojure
  • Erlang
  • Elixir
  • Gleam
  • Rust
  • OCaml
  • Clean
  • F#
  • Idris
  • Agda
  • Lean
  • Lean 4
  • Coq
  • F*
  • Miranda
  • Standard ML
  • ML
  • Caml
  • Mercury
  • Datalog
12

Interpreted (compiler in the runtime)

A major implementation runs source directly, with no separate build step, because the compiler ships as a library inside the runtime: it loads the source, compiles it to bytecode and/or machine code on the fly, and executes it in the same process. This runtime-hosted compiler is the hallmark of a dynamic language, as opposed to an ahead-of-time toolchain that produces a standalone binary.

  • Python
  • Ruby
  • PHP
  • Lua
  • R
  • Perl
  • Wolfram Language
  • APL
  • BASIC
  • Forth
  • JavaScript
  • SQL
19

JIT-compiled

A major implementation translates source, bytecode, or emitted code to native instructions during execution or module loading. Some JITs optimise frequently executed paths adaptively; others perform non-adaptive load-time compilation.

  • Java
  • C#
  • JavaScript
  • TypeScript
  • Julia
  • Erlang
  • Elixir
  • Kotlin
  • Scala
  • Groovy
  • Clojure
  • Gleam
  • PHP
  • F#
  • Visual Basic
  • Self
  • Smalltalk
  • Ruby
  • Lua
5

Lazy evaluation

The language uses call-by-need broadly or delays important constructs such as function arguments until their values are demanded. Laziness can support infinite data and avoid unused work, but its scope and performance effects vary by language.

  • Haskell
  • Miranda
  • Clean
  • SASL
  • R
7

Lightweight concurrency

Major runtimes or libraries can multiplex many lightweight tasks, such as goroutines, BEAM processes, coroutines, or virtual threads, over operating-system threads. Their stacks or suspended state are represented differently, and capacity and scheduling behavior depend on the implementation and workload.

  • Go
  • Erlang
  • Elixir
  • Gleam
  • Haskell
  • Java
  • Kotlin
10

Manual memory

The language supports explicit allocation and deallocation, such as malloc/free, new/delete, storage pools, or equivalent facilities. Idiomatic programs may also use scopes, RAII, or managed types, but incorrect manual lifetime handling can cause leaks, crashes, or security vulnerabilities.

  • C
  • C++
  • Fortran
  • Pascal
  • Ada
  • Zig
  • Forth
  • COBOL
  • PL/I
  • Delphi
7

Monads / typed effects

The language uses monadic interfaces, effect types, managed commands, or uniqueness/world types to represent at least some effects explicitly. Coverage varies by language, and these abstractions improve composition and reasoning without eliminating effect-related bugs.

  • Haskell
  • PureScript
  • Idris
  • Elm
  • Clean
  • F*
  • Lean 4
10

Null safety

The type system distinguishes potentially absent values, often through Option/Maybe or nullable types, and requires many uses to handle that case explicitly. Configuration, interoperability, assertions, or unsafe escape hatches may still permit null-related runtime failures.

  • Rust
  • Kotlin
  • Swift
  • TypeScript
  • F#
  • OCaml
  • Haskell
  • Elm
  • Gleam
  • PureScript
3

Ownership & affine types

The type system restricts how values or resources are copied, moved, borrowed, dropped, or destroyed. Depending on the language, this can enforce memory safety, resource discipline, or VM invariants without requiring every value to be managed by a tracing garbage collector.

  • Rust
  • Move
  • Cairo 1
30

Parametric polymorphism

A function or data structure can be parameterised over types, such as List<T>, without duplicating its source. Implementations may erase parameters, share runtime code, pass type information through dictionaries, or generate specialised code.

  • C++
  • Java
  • C#
  • Kotlin
  • Scala
  • Go
  • Rust
  • Swift
  • TypeScript
  • Haskell
  • OCaml
  • F#
  • Standard ML
  • Ada
  • D
  • Nim
  • ML
  • Caml
  • Miranda
  • Clean
  • Eiffel
  • Crystal
  • PureScript
  • Elm
  • Mercury
  • Move
  • Cairo 1
  • Zig
  • Julia
  • Gleam
11

Runs on a managed VM

A major implementation runs compiled code on a managed virtual machine such as the JVM, CLR, or BEAM. The VM commonly supplies automatic memory management, portability services, and runtime tooling, while deployment behavior and features vary by platform.

  • Java
  • Kotlin
  • Scala
  • Groovy
  • Clojure
  • C#
  • F#
  • Visual Basic
  • Erlang
  • Elixir
  • Gleam
4

Scripting (glue / automation)

Designed to orchestrate other programs and automate tasks (running commands, moving files, wiring pipelines) rather than to build large standalone applications. A classic shell is read and executed command by command, with no compiler producing a program ahead of time. Embeddable dynamic languages such as Lua sit on the border: widely used for scripting, but backed by a real bytecode compiler in their runtime.

  • sh
  • AWK
  • Lua
  • Perl
64

Statically typed

The language checks declared or inferred type constraints before execution. Gradual and dynamically extensible systems may retain escape hatches or defer some checks, but static checking can catch many mismatches before affected code runs.

  • C
  • C++
  • C#
  • Java
  • Kotlin
  • Scala
  • Go
  • Rust
  • Swift
  • TypeScript
  • Haskell
  • OCaml
  • F#
  • Standard ML
  • Ada
  • Zig
  • Nim
  • D
  • Crystal
  • Elm
  • PureScript
  • Gleam
  • Solidity
  • Fortran
  • COBOL
  • Pascal
  • Mercury
  • Caml
  • ML
  • Simula 67
  • Eiffel
  • Delphi
  • Idris
  • Agda
  • Coq
  • Rocq
  • Lean
  • Lean 4
  • F*
  • PL/I
  • Move
  • Cairo 1
  • Vyper
  • Leo
  • Noir
  • Michelson
  • Dafny
  • Why3
  • Objective-C
  • ALGOL 60
  • ALGOL 68
  • ALGOL 58
  • Mojo
  • Carbon
  • Miranda
  • Clean
  • HOL
  • Isabelle
  • PVS
  • Alloy
  • Nuprl
  • LCF
  • Twelf
  • Automath