Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Yes. A TypeScript type guard can keep compiling while its runtime check no longer proves the type it declares. The compiler trusts the return annotation value is SomeType and narrows call sites to SomeType whether or not the function body establishes that. The TypeScript 5.5 release notes say it directly: “Explicit type predicates (β€œis”) are no safer than a type assertion (β€œas”).” The rest of this article explains how drift happens, how to reduce it, and where the compiler and linters stop helping.

What a type predicate promises the compiler

A user-defined type guard is a function whose return type is a type predicate. In the TypeScript Handbook’s Narrowing chapter, the pattern looks like this:

type User = { id: string; email: string };

function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null && "id" in value;
}

function greet(input: unknown) {
  if (isUser(input)) {
    // input is treated as User here
    console.log(input.email);
  }
}

Inside greet, the compiler does not re-run the logic of isUser or compare it with User. It reads the signature, sees value is User, and narrows input. The declared target type and the implemented check are two separate claims, and only the declaration is enforced by the type system.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How a guard drifts

Drift happens when the type changes and the check does not, or when the check was incomplete from the start. Suppose the team later adds a required field to User:

type User = { id: string; email: string; role: "admin" | "member" };

function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null && "id" in value;
}

The function still type-checks. The annotation still says value is User, and nothing in the implementation contradicts the compiler. But an object with an id and nothing else now passes as a User, so input.email and input.role can be undefined at runtime while the type says they are present.

A guard can also be wrong on day one. One that checks a single property, or relies on truthiness where it needs presence, claims more than it tests. This is a consequence of the trust model, not a measured rate. The TypeScript documentation does not publish how often guards drift in real codebases, and this article does not estimate it.

Three habits that keep the claim honest

None of these steps makes the compiler verify a guard. They narrow the gap between the declared type and the runtime test so a reviewer can close it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
TypeScript Programming Language - Software Engineer & Coder T-Shirt
  • TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
  • TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem

1. Check every property the code relies on

Write the runtime test against the full contract the code depends on, not the first property that identifies the value. When the type changes, change the check in the same commit. A useful review question is: “If I add a required field to this type, what in this function has to change?” If the answer is “nothing,” the guard is probably under-checking.

2. Test both directions of the predicate

A predicate makes two claims. When it returns true, the value should really be in the type. When it returns false, the value should really be outside it. The TypeScript 5.5 release notes describe type predicates as “if and only if,” so tests should cover both sides. Useful inputs include:

  • a fully valid value, which must return true
  • a value missing each required property in turn, which must return false
  • falsy but valid values, such as 0 for a number, "" for a string, or false for a boolean
  • null and undefined, which often expose a missing null check

3. Prefer precise checks over truthiness

Truthiness tests discard valid values. The TypeScript 5.5 release notes use this comparison to show the difference:

function getScore(score: number | undefined) {
  if (!!score) {
    // 0 is excluded here, even though 0 is a valid number
  }

  if (score !== undefined) {
    // only undefined is excluded
  }
}

The second form states exactly which value is excluded. When the intent is “a number was provided,” writing that comparison keeps the code and any predicate built on it aligned with the claim.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Explicit predicates and inferred predicates

TypeScript 5.5 added inference for type predicates in certain simple functions. When a function qualifies, the compiler derives the predicate from its body, so no separate value is SomeType annotation has to be maintained. The release notes list the conditions, and they are narrow:

  • the function has no explicit return type annotation
  • it has a single return and no implicit returns
  • it does not mutate its parameter
  • it returns a boolean expression that refines the parameter, with the false branch exactly the complement of the true branch

A function that meets those conditions looks like this:

function isString(x: unknown) {
  return typeof x === "string";
}
// Inferred as (x: unknown) => x is string

Inference is not validation. It derives the predicate from logic that is still written by hand. If the body is wrong, the inferred predicate is wrong too, and the compiler will not object. Inference removes one maintained claim when the conditions apply, and nothing more. Functions with a body that checks a property, calls another helper, or needs an explicit annotation for readability still need an explicit predicate and the review habits above.

Assertions and external data

An as SomeType assertion and an explicit value is SomeType annotation are both trust boundaries. The TypeScript Handbook’s Basic Types chapter states that type assertions have no runtime effect. Code that receives data from outside the program, such as API responses, parsed JSON, files, or form input, should validate the actual structure at runtime before the narrowed type is used. Whether that validation is a hand-written check or a schema library is a design choice; the compiler does not provide it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which mechanism does what

Mechanism Who verifies the claim Runtime effect Typical use
Explicit type predicate (value is T) Nobody. The compiler trusts the annotation. The guard’s body runs; only its logic establishes the claim. Complex checks, reviewed and tested carefully
Inferred predicate (TypeScript 5.5 and later) The compiler derives the predicate from the body, only when the listed conditions hold. The same body runs. Simple boolean refinements with no annotation to keep in sync
Type assertion (as T) Nobody. None. Only when the value was verified elsewhere
Runtime validation of external data The validation code itself, executed on the value. Yes. The value is checked as it arrives. API responses, parsed JSON, files, user input
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

What linting and compiler diagnostics can and cannot catch

Compiler diagnostics and lint rules catch some bug classes, but none of them proves that an explicit predicate matches its implementation. TypeScript 5.6 added checks for certain conditions whose outcome is fixed by their syntax, such as always-truthy expressions and some nullish checks. Those diagnostics are useful, but they answer a different question from whether a declared predicate matches its body.

The typescript-eslint rule strict-boolean-expressions flags values used in boolean contexts, which can surface truthiness shortcuts like !!score. It is a guardrail for one habit. It does not evaluate a guard’s logic against its declared type, and a team that relies on it for that purpose will still ship drift.

Version and currency notes

The behavior described here comes from the TypeScript 5.5 and 5.6 release notes and from Handbook pages reviewed in October 2026. Inference conditions and diagnostics can change between releases, so check the current release notes before relying on a specific rule. This article does not establish which TypeScript release is the newest as of publication, and it does not measure how often type guards drift in practice.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.