Back to News & Insights
Web Development September 23, 2026 Β· 6 min read

Your Type Guard Can Silently Drift from Your TypeScript Type πŸ”§

Hoi hoi! πŸ‘‹ I'm @nyaomaru, a frontend engineer just back from a short vacation on Texel, a small...

Your Type Guard Can Silently Drift from Your TypeScript Type πŸ”§

I'm @nyaomaru, a frontend engineer just back from a short vacation on Texel, a small island in the Netherlands. 😸🏝️

It doesn't prove that your runtime checks actually validate every field in User.

And that's where type guards can slowly drift away from the types they claim to protect.

But it cannot generally prove that arbitrary runtime logic actually validates every part of that type.

The return type is a contract written by us, not a proof generated from the function body.

And every time that happens, we need to remember that some runtime guard somewhere may also need an update.

But at compile time, TypeScript can check whether the guards we declared match the object type they're supposed to follow.

I like keeping these two decisions explicit: optionalKey(...) β†’ the property may be absent nullable(...) β†’ the value may be null

These are two different questions: Does my guard definition match the TypeScript type? Should a runtime object be allowed to contain additional properties?

If you want the runtime object shape to be closed as well, you can enable exact mode:

It shouldn't be confused with keeping the guard definition synchronized with the TypeScript type.

Useful when User already exists and the runtime guard needs to stay aligned with it.

A schema library or code generation may be the better source of truth when you need things like: structured validation errors coercion transforms defaults generated artifacts

Types are erased at runtime, so you still need to declare the guards you want to execute.

It also doesn't: prove that every custom predicate is honest coerce values return rich structured validation errors replace schema-first workflows validate numeric or symbol properties as part of its string-keyed object contract

doesn't mean TypeScript inspected your implementation and proved that every User field was validated.

So when a TypeScript type is the source of truth, I think it's useful to make the runtime guard structurally depend on that type instead of relying on us to remember every future change.

If an existing TypeScript type should define the contract, connect the guard to that type.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation