JavaScript and Sound Types

Published Sat Feb 24 2024

Today when a JavaScript developer is looking to introduce static typing TypeScript really is the only option. With the majority of NPM having TypeScript support it is hard to make an argument for anything else. Before this was the state of the ecosystem though, Facebook’s Flow was a strong competitor. One of Flow’s goals was to create a sound type system atop JavaScript, a publicly stated non-goal of Typescript:

Non-goals


3. Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.

What are “sound” types?

A type system is sound if compiled code cannot throw runtime type errors. Broadly this means that a variable of a given type will always be that type when the code is run. With TypeScript we code defensively, but know type safety does not extend past compilation. Here is a description from the Flow docs:

In type systems, soundness is the ability for a type checker to catch every single error that might happen at runtime. This comes at the cost of sometimes catching errors that will not actually happen at runtime.

While soundness has a place and is worthy of pursuing, TypeScript was designed to not have this. Why? To make itself more useful as a tool in the JavaScript ecosystem.

TypeScript has grown alongside existing, untyped, JavaScript code. It is designed to be incrementally adoptable so that large projects could move towards it gradually. If designed to be sound, these benefits would be in jeopardy.

type MyType = { [key: string]: number };
const data = JSON.parse(jsonString) as MyType;

As an example, here is some code that calls JSON.parse() then casting it as MyType. This code is unsound. TypeScript has no way to know what this string of JSON will contain at runtime. However, it allows you to write this because you might know better. Many developers will cast a type like this while working with an API response because they know the shape of that API’s data and TypeScript does not.