Skip to content

Software Quality

Static types

Static types declare the shape of every value a program handles, and the compiler enforces that declaration before the program runs. Mainstream static typing is the type-checking layer most working programmers encounter day-to-day — TypeScript, Python with mypy, Kotlin, Swift, Haskell, OCaml. The technique catches a specific class of representational, null, and shape errors.

What it catches

  • Representational errors. Treating a string as a number, swapping argument order, calling a method that doesn't exist.
  • Shape errors. Field renames, missing branches in a switch over a sum type, undefined-when-expected-defined.
  • Null and missing-value errors (in systems with non-null types). Optional vs required is checked at every call site.
  • Refactor safety. With full type coverage, the compiler points at every call site a field rename breaks.
  • API drift. A library that changed its signature fails the consumer's type check on the next bump.

What types do not catch: logic bugs that respect the declared types, off-by-one errors at runtime, anything beyond the type system's expressive reach. Sound types prove the absence of one bug class, never the presence of correctness.

Patterns worth knowing

The following type-system patterns recur across stacks and repay their configuration cost.

  • Make impossible states impossible (the Elm pattern). Encode domain states as a sum type — Loading | Loaded(Data) | Failed(Error) — so the compiler refuses code that tries to read Data from a Loading value.
  • Exhaustive pattern matching. When the type system enforces every case of a sum type, adding a variant makes the compiler flag every match that misses it, a check that strings in domain code cannot get.
  • Opaque / nominal types to prevent accidental mixing. A Metres and a Feet that are both f64 underneath are still mutually unassignable.
  • Newtype wrappers for invariants. EmailAddress, UserId, NonEmptyString — a constructor enforces the invariant once; thereafter the type guarantees it.
  • IO validation at the boundary. An unsound type system does not check deserialized data against its declared type; boundary validators (Zod, io-ts, valibot, Pydantic) generate the runtime check and the static type from one schema (schema and boundary validation).

Tools

  • TypeScript with strict: true (especially strictNullChecks, noUncheckedIndexedAccess, exactOptionalPropertyTypes). The canonical gradual-typing case study.
  • Python with mypy --strict or pyright / basedpyright. Each checker is gradual and unsound by design, as TypeScript is.
  • Kotlin with -Xexplicit-api=strict; nullable types via ?; sealed classes for sum types.
  • Swift — non-null by default; optionals are explicit; exhaustive switch over enums.
  • Haskell with strict warnings (-Wall -Werror); algebraic data types; type classes; higher-kinded types.
  • OCaml / F# — Hindley-Milner inference, sum types, exhaustiveness checking.
  • Scala 3 — strong sum types via enums; opaque types; match expressions with exhaustiveness.
  • Java with record (since 16) and sealed classes (since 17), null annotations (JSR-305 / Checker Framework / NullAway).
  • C# with nullable reference types and record types.
  • Go generics (since 1.18) — relatively limited; the type-system case for Go remains modest by comparison.

When to use, when not

Use:

  • Every new project in a typed or gradually-typable language. The marginal cost of turning on strict mode at project start is low.
  • Existing projects in a typed language with strictness flags off. A team ratchets one flag at a time and fixes the diffs it exposes, so each step costs no more than that flag.
  • API boundaries. A type at the boundary documents the contract for humans and compilers.

Don't:

  • Treat types as a substitute for tests. Types check shape; tests check behavior.
  • Treat unsound types as a substitute for runtime validation at the boundary. Deserialized data then reaches typed code unchecked.
  • Lean on any / Object / dynamic. Each use is a hole in the proof.

Evidence

  • Gradual typing catches a specific bug class. Around 15% of public JavaScript bug-fix commits in the studied sample would have been caught by gradual typing (Gao et al. 2017)1.
  • But not the overall bug rate. Across 604 JS/TS GitHub projects, code quality and complexity were significantly better in the TS sample, while bug proneness was not significantly lower: the TS sample's mean bug-fix commit ratio was higher (Bogner and Merkel 2022)2.
  • A development-time penalty in a controlled experiment. Among 49 subjects implementing a parser over 27 hours, the statically typed group took significantly longer to reach a working scanner, and the test cases the finished parsers passed showed no significant difference (Hanenberg 2010)3.
  • Cross-language defect study, contested and unresolved. An initial mining study reported small cross-language effects (Ray et al. 2014)4; a reproduction could not confirm most of them after methodology corrections (Berger et al. 2019)5; the original authors rebutted the reproduction, arguing their (small) effects still stand (Ray et al. 2019)6; and an independent Bayesian reanalysis of the same data left the question unsettled (Furia et al. 2022)7. Weak, disputed evidence either way.
  • Industrial anecdote. A review of six months of Airbnb's incident postmortems estimated ~38% of the incident-causing bugs could have been prevented by TypeScript (Bunge 2019)8 — a single company's classification rather than a controlled study, and 2.5× the effect the gradual-typing study measured.

The support for a sound type system's guarantee is definitional, not statistical.

Classification

  • Quality dimensions: Functionality, Maintainability.
  • Area: General programming; any compiled or gradually-typed language; the everyday static-checking layer in modern stacks.
  • Guarantee: Exhaustive within what the type system can express — only when the system is sound. Most mainstream type systems are deliberately unsound at one or more boundaries; see the types overview for the soundness discussion.

Referenced by

References


  1. Gao, Zheng, Christian Bird, and Earl T. Barr. 2017. "To Type or Not to Type: Quantifying Detectable Bugs in JavaScript." Proceedings of the 39th International Conference on Software Engineering (ICSE '17), 758–69. https://doi.org/10.1109/ICSE.2017.75. ↩

  2. Bogner, Justus, and Manuel Merkel. 2022. "To Type or Not to Type? A Systematic Comparison of the Software Quality of JavaScript and TypeScript Applications on GitHub." Proceedings of the 19th International Conference on Mining Software Repositories (MSR '22), 658–69. https://doi.org/10.1145/3524842.3528454. ↩

  3. Hanenberg, Stefan. 2010. "An Experiment about Static and Dynamic Type Systems: Doubts about the Positive Impact of Static Type Systems on Development Time." Proceedings of OOPSLA '10, 22–35. https://doi.org/10.1145/1869459.1869462. ↩

  4. Ray, Baishakhi, Daryl Posnett, Vladimir Filkov, and Premkumar Devanbu. 2014. "A Large Scale Study of Programming Languages and Code Quality in GitHub." Proceedings of the 22nd ACM SIGSOFT International Symposium on Foundations of Software Engineering (FSE '14), 155–65. https://doi.org/10.1145/2635868.2635922. ↩

  5. Berger, Emery D., Celeste Hollenbeck, Petr Maj, Olga Vitek, and Jan Vitek. 2019. "On the Impact of Programming Languages on Code Quality: A Reproduction Study." ACM Transactions on Programming Languages and Systems 41 (4): 1–24. https://doi.org/10.1145/3340571. ↩

  6. Ray, Baishakhi, Premkumar Devanbu, and Vladimir Filkov. 2019. Rebuttal to Berger et al., TOPLAS 2019. arXiv:1911.07393. https://doi.org/10.48550/arXiv.1911.07393. ↩

  7. Furia, Carlo A., Richard Torkar, and Robert Feldt. 2022. "Applying Bayesian Analysis Guidelines to Empirical Software Engineering Data: The Case of Programming Languages and Code Quality." ACM Transactions on Software Engineering and Methodology 31 (3): 1–38. https://doi.org/10.1145/3490953. ↩

  8. Bunge, Brie. 2019. Adopting TypeScript at Scale. https://www.youtube.com/watch?v=P-J9Eg7hJwE. ↩