Type systems

Three independent questions are often collapsed into the single word “typed”: when types are checked, how strictly conversions are policed, and how much the programmer must write down. This atlas records all three separately.

Three separate questions

When: static or dynamic

Static checking rejects programs before they run; dynamic checking raises errors at the point of misuse. The distinction is about timing, not about whether types exist.

Static: C, Java, Rust, Swift · Dynamic: Python, Ruby, JavaScript

How strictly: strong or weak

A strong system refuses to reinterpret a value as an unrelated type; a weak one silently coerces. Strength is a spectrum and is orthogonal to timing.

Weak and static: C · Strong and dynamic: Python

How much is written: manifest or inferred

Inference lets the compiler reconstruct types the programmer omitted. It changes ergonomics, not safety, and can slow compilation when unconstrained.

Full inference: Haskell, OCaml · Local only: Java, Go

Common confusion. C is statically but weakly typed; Python is dynamically but strongly typed. The two axes are independent, and a language may be strict on one and permissive on the other.
Static and weakStatic and strongDynamic and weakDynamic and strongWeakMixedStrongDynamicGradualStaticCCC++C++JavaJavaC#C#GoGoRustRustSwiftSwiftHaskellHaskellTypeScriptTypeScriptPythonPythonJavaScriptJavaScriptRubyRubyClojureClojurePHPPHPErlangErlangStrength: values are not silently reinterpretedTiming: checking happens before running
Figure 1. Fifteen languages on the two questions that are most often conflated. Static versus dynamic is when checking happens; strong versus weak is whether the language will silently reinterpret a value. They are independent, and all four quadrants are occupied. Positions are ordinal readings of the specification.

Back to top

Type-system matrix

Fifteen languages across the three questions, plus the polymorphism mechanism each provides.

Language Checked Strength Annotation burden Polymorphism Notable characteristic
C Static Weak Manifest None Implicit conversions between numeric and pointer types
C++ Static Mostly strong Partly inferred (auto) Templates, concepts Inherits C's conversions; concepts constrain templates
Java Static Strong Manifest, local var inference Generics (erased) Erasure removes parameter types at runtime
C# Static Strong Partly inferred (var) Generics (reified) Type arguments survive to runtime
Go Static Strong Partly inferred (:=) Generics since 1.18 Structural interfaces, nominal named types
Rust Static Strong Inferred (Hindley-Milner style) Generics, traits Lifetimes are part of the type
Swift Static Strong Inferred Generics, protocols, associated types Optionals make nullability a type distinction
Haskell Static Strong Fully inferred Type classes, higher-kinded types Purity tracked in the type via monads
OCaml Static Strong Fully inferred Parametric polymorphism, modules Structural typing for objects and rows
TypeScript Static (erased) Strong by policy Inferred Generics, conditional and mapped types Types vanish at runtime; unsound by design in places
Python Dynamic Strong Optional annotations Generics; native syntax since 3.12 Annotations are not enforced by the interpreter
JavaScript Dynamic Weak None None Coercion across types in comparison and arithmetic
Ruby Dynamic Strong Optional (RBS/Sorbet) None in core Duck typing is the intended discipline
Clojure Dynamic Strong Optional specs None Validation is a runtime library, not a checker
Erlang Dynamic Strong Optional (Dialyzer specs) None Success typing finds contradictions, not all errors

Sources: language specifications and reference documentation for each language's current stable release.

Static checked before execution Dynamic checked during execution GC automatic, collector-driven Manual programmer-directed Ownership/ARC compile-time or refcount discipline Unspecified varies by implementation

Back to top

Nullability as a type-system decision

The most consequential type-system change of the last two decades is the decision to make the absence of a value visible in the type. Where a reference type may be null implicitly, every dereference is a potential failure the compiler cannot see; where absence is a distinct type, the compiler forces the case to be handled.

Implicit null

Any reference may be null. Failures surface at runtime as null-pointer errors.

C, C++, Java, C#, Go

Option type

Absence is a separate wrapping type that must be unwrapped before use.

Haskell, OCaml, Rust, Swift

Retrofitted nullability

Nullable and non-nullable variants of existing types, checked by the compiler or an analyser.

Kotlin, C# 8+, TypeScript strict

Not applicable

Dynamically typed languages carry a null-like value but check only at use.

Python, Ruby, JavaScript

Swift's Optional is the fullest mainstream example: the language provides both a chaining operator that propagates absence and a force-unwrap operator that converts absence into a fatal error, so that the risky operation is syntactically visible. See Optionals and chaining.

Back to top