Satisfying TS

post 128 words #typescript

I see as used a lot in TypeScript, and it is one of my pet hates. It throws away type checking by asserting “I know better than the compiler”, which is often not true.

A common case is filtering a discriminated union array down to one variant. Say you have a chat app, people reach for an assertion like this:

type UpdateNewMessage = { type: "UpdateNewMessage"; text: string };
type UpdateUserJoined = { type: "UpdateUserJoined"; username: string };

type Update = UpdateNewMessage | UpdateUserJoined;

const allUpdates: Update[] = getUpdates();

const newMessageUpdates = allUpdates.filter(
  (update) => update.type === "UpdateNewMessage",
) as UpdateNewMessage[];

This compiles, but you have forced the array to be a UpdateNewMessage[] even if your filter is wrong.

Instead, use a type predicate to let the compiler narrow safely:

type UpdateNewMessage = { type: "UpdateNewMessage"; text: string };
type UpdateUserJoined = { type: "UpdateUserJoined"; username: string };

type Update = UpdateNewMessage | UpdateUserJoined;

function isUpdateNewMessage(update: Update): update is UpdateNewMessage {
  return update.type === "UpdateNewMessage";
}

const newMessageUpdates = allUpdates.filter(isUpdateNewMessage);
// inferred as UpdateNewMessage[]

The predicate keeps the check in one place, and you keep full type safety without a cast. These functions can live alongside your type declarations so your runtime checks stay aligned with your types.

Much more satisfying :D

More details: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates