Typescript

TypeScript Lies? The Hidden Risks of Using Predicates!

1

TypeScript predicates are not truly safe!

At first glance, a function like isString seems harmless. It tells TypeScript: "If this function returns true, you can trust that the input is a string." But what TypeScript doesn’t do is verify the logic inside that function. You, the developer, are now responsible for proving that assertion is correct. TypeScript simply takes your word for it.

Let’s look at a subtly broken example:

function isUser(obj: any): obj is { name: string } {
return 'name' in obj;
}

This function claims that any object with a name property is a valid User — but it doesn’t check the type of name, or whether the object is even non-null. So this passes:

const maybeUser = { name: null };
if (isUser(maybeUser)) {
// TypeScript thinks maybeUser.name is a string
maybeUser.name.toUpperCase(); // 💥 Runtime crash
}

TypeScript happily trusts that name is a string because that’s what the predicate promised. But the predicate logic didn’t deliver.

The Illusion of Safety

This is the core problem with type predicates: they are only as safe as the logic inside them, but TypeScript provides no enforcement. It's a handshake agreement between the developer and the compiler. If the developer breaks that agreement, the compiler still behaves as if nothing is wrong.

Even worse, TypeScript’s type system cannot validate the correctness of a predicate. It has no insight into whether the logic inside isUser is sound. This leads to a false sense of safety — one that doesn’t show up until runtime.

When Things Go Wrong

Here's another problematic scenario:

function isNumberArray(input: any): input is number[] {
return Array.isArray(input);
}

This passes:

const data: unknown = [1, "two", 3];
if (isNumberArray(data)) {
data.forEach(num => console.log(num.toFixed(2))); // 💥 Runtime crash on "two"
}

The type predicate says it's a number array — and TypeScript believes it. But it’s not.

Best Practices (or How to Tread Carefully)

  • Always validate all assumptions in your predicate — types, structure, null checks, etc.

  • Avoid writing broad predicates that pass easily; be specific.

  • Don’t over-trust predicates from third-party code unless you’ve audited their logic.

  • Consider using runtime validation libraries like Zod or io-ts for more robust checks.

Final Thoughts

TypeScript’s type guards and predicates are a powerful tool, but they are a double-edged sword. They can help narrow types and improve developer ergonomics, but they also introduce subtle bugs when misused. The compiler won’t warn you — and that’s the problem.

So use predicates with caution — and always assume they can lie.