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.
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.
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.
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.
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.
Option type
Absence is a separate wrapping type that must be unwrapped before use.
Retrofitted nullability
Nullable and non-nullable variants of existing types, checked by the compiler or an analyser.
Not applicable
Dynamically typed languages carry a null-like value but check only at use.
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.