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 at zero per-bug cost.
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. 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 readDatafrom aLoadingvalue. - Exhaustive pattern matching. When the type system enforces every case of a sum type, the compiler catches the case you forgot when you added a new variant — the strongest argument for sum types over strings in domain code.
- Opaque / nominal types to prevent accidental mixing. A
Metresand aFeetthat are bothf64underneath are still mutually unassignable. The Mars Climate Orbiter is the canonical example of what happens without this. - Newtype wrappers for invariants.
EmailAddress,UserId,NonEmptyString— a constructor enforces the invariant once; thereafter the type guarantees it. - IO validation at the boundary. Static types vanish at serialization; boundary validators (Zod, io-ts, valibot, Pydantic) generate runtime checks from the same schema. See schema and boundary validation for the runtime side.
Tools¶
- TypeScript with
strict: true(especiallystrictNullChecks,noUncheckedIndexedAccess,exactOptionalPropertyTypes). The canonical gradual-typing case study. - Python with mypy --strict or pyright / basedpyright. Same trade-offs as TypeScript.
- Kotlin with
-Xexplicit-api=strict; nullable types via?; sealed classes for sum types. - Swift — non-null by default; optionals are explicit;
exhaustive
switchover 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
nullablereference 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 near-zero.
- 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 prove shape; tests prove behavior.
- Treat unsound types as a substitute for runtime validation at the boundary. Zod, Pydantic, Joi exist precisely because unsound types don't survive deserialization.
- 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-rate reduction was not statistically significant (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. Airbnb's 2019 internal postmortem estimated ~38% of post-mortemed 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.
No result here quantifies a caught bug class outside gradually-typed JavaScript, so the stronger guarantee a sound type system offers stands unmeasured at comparable scale.
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¶
- Effect scope · The axes
- Contracts and runtime assertions · Methods
- Dead-code detection · Methods
- Linear types · Methods
- Schema and boundary validation · Methods
- Types and effects · Methods
- Verifying memory safety · Methods
- Verifying time and date handling · Methods
- TypeScript · Recipes
- How AI fits into software quality · AI
- Glossary · Overview
References¶
-
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. ↩
-
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. ↩
-
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. ↩
-
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. ↩
-
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. ↩
-
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. ↩
-
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. ↩
-
Bunge, Brie. 2019. Adopting TypeScript at Scale. https://www.youtube.com/watch?v=P-J9Eg7hJwE. ↩