This article is a part of the systemic-ts series.

Source of Complication: Type Definitions Stripped At Runtime

Thankfully, now there are many solutions out there to validate type at runtime. Almost too many. But these validators are optional, so the problem remains for those who don't use them.

TypeScript code, when compiled with a pure TypeScript compiler, has all the type definitions stripped. Code that assumes unknown or any with as may fail.

type Circle = { diameter: number };
const circle = (await receiveCircle()) as Circle; // `unknown` asserted as 'Circle'

const diameter = Math.PI * circle.diameter; // possibly NaN

This problem often arises on the JavaScript runtime's system's boundary e.g. network call, file access, inter-process communication.

Anyway, when people start to realize, it is duct-taped with manual runtime type checking

if ("diameter" in circle && typeof circle.diameter === "number") {
  // valid
} else {
  // invalid

Soon after, genius folks created validators like zod and io-ts. With that, defining the type and validator becomes a one-step process.

const Circle = t.type({
  diameter: t.number,
const maybeCircle = await receiveCircle();
if (! {
  throw new Error("not circle");
const circle = maybeCircle;

The lack of a native validator isn't as depressing as other sources of complications. But the fact that TypeScript is in a way weak-typed forces us to treat these third-party validators as essentials.