Paradigms

A paradigm is a way of expressing computation, not a category a language belongs to. Nearly every language in this atlas supports several; what distinguishes them is which paradigm the idiomatic style and the standard library assume.

Paradigm groups

Six groups, each with the trade-off it makes. Listed languages are those whose idiomatic style leans on the paradigm, not the complete set that permits it.

Paradigm Model of computation Representative languages What it buys What it costs
Imperative Computation is a sequence of statements that change program state. Control flow is explicit; the machine model is visible. Fortran, C, Pascal, Go Direct mapping to hardware; predictable cost State is global by default, making local reasoning harder
Object-oriented State and the operations on it are bundled into objects that interact by method call or message send. Behaviour is selected by the receiver's type. Smalltalk, Java, C#, Ruby Encapsulation and substitutability across large teams Inheritance hierarchies couple types that need not be related
Functional Computation is the application of functions to values. Functions are first-class, and data is preferably immutable. Haskell, OCaml, Clojure, F# Referential transparency makes code easy to test and parallelise Persistent data structures add allocation and indirection
Declarative / logic The program states what must hold rather than how to compute it; a solver or planner supplies the strategy. Prolog, SQL, Datalog Concise problem statements; the engine may optimise freely Performance depends on an optimiser the author cannot always see
Generic / metaprogramming Code is written against type parameters or generated at compile time, so one definition serves many concrete types. C++, Rust, Zig, Scala Abstraction with no runtime dispatch cost Error messages and compile times degrade with template depth
Concurrent / dataflow The unit of composition is an independently executing process or stream, connected by messages or channels rather than shared memory. Erlang, Elixir, Go, Swift Failure and load can be isolated per process Message protocols become an unchecked interface unless typed

Sources: language specifications and reference documentation. Trade-off columns summarise widely documented consequences and are not survey results.

Back to top

Convergence

Functional closures, ADTs, pattern matching Systems value types, ownership Mainstream Java, C#, Python, JavaScript, Swift Result multi-paradigm default
Figure 1. Feature migration since 1990. Closures, algebraic data types, and pattern matching moved from the ML tradition into mainstream languages; value semantics and ownership moved from the systems tradition. Diagram: Programming Language Atlas.

Paradigms are no longer a partition

Between 1990 and 2010 the mainstream languages absorbed features that had been considered functional research: lexical closures reached Java in 2014 and C++ in 2011, algebraic data types and exhaustive pattern matching reached C#, Java, Python, and Swift in the following decade.

The same happened in the other direction. Value semantics, once a systems-language concern, became a first-class design point in Swift, and ownership discipline moved from research languages into Rust and then, in narrower form, into C++ and Swift.

The practical consequence is that paradigm is now a weak predictor of what a language can express, and a strong predictor of what its standard library and community consider idiomatic. This atlas therefore records paradigm emphasis rather than a single paradigm label.

Object-orientedencapsulated state, dispatch on the receiverFunctionalfirst-class functions, immutable dataBoth, idiomaticallyScala, Swift, Kotlin, F#, OCaml, Rust
Figure 2. Two paradigm labels that are routinely treated as alternatives, and the entries that carry both. The intersection is where most of the index now sits, which is the case for recording paradigm emphasis rather than paradigm membership.

Back to top

Concurrency models

Concurrency is treated as a separate axis because a language's concurrency model is often chosen independently of its paradigm emphasis.

Model Mechanism Representative languages Principal failure mode
Threads and locks OS threads over shared mutable memory, guarded by mutexes and condition variables. C, C++, Java, C# Data races and deadlocks are the programmer's responsibility
Actors Independent processes owning private state, communicating only by asynchronous message. Erlang, Elixir, Swift, Akka (Scala) Mailbox growth and message ordering become the failure modes
CSP channels Lightweight processes passing values through typed channels; the channel, not the data, is shared. Go, occam, Clojure core.async Channel topology can deadlock as easily as locks
Async/await Suspension points marked in the source; a scheduler interleaves tasks on a small thread pool. JavaScript, Python, C#, Rust, Swift Function colouring splits the library ecosystem in two
Data parallel One operation applied across a whole array or collection, with the runtime choosing the split. Fortran, APL, Julia, CUDA C++ Only fits problems that are naturally uniform

Languages frequently support more than one model; the entry lists the model its standard library makes primary. See the Swift article for a worked example combining async/await with actors.

Locksdisciplineshared memoryChannelsconventionone ownerActorsruntimeprovedChecked isolationcompiler
Figure 3. Where each concurrency model puts the boundary that protects shared state. Reading left to right, the protection moves from the programmer's discipline into the type system: the failure mode changes from a data race that is invisible at compile time to a compile error.

Back to top