Skip to main content

Type Guards

Type guards are functions whose return type is annotated with param is PredicateType, narrowing the parameter's type when they return true.

function predicate(param: InputType): param is PredicateType {
return <some_expression>;
}

The type of this function can also be written as a type annotation:

type PredicateFunc = (param: InputType) => param is PredicateType;

When to use this

Use type guards when built-in refinements (typeof, instanceof, equality checks) aren't sufficient to narrow a type. They let you encapsulate custom narrowing logic in a reusable function — for example, filtering arrays to a specific subtype with .filter(), or checking domain-specific invariants that Flow can't infer on its own. Use one-sided type guards (implies) when the predicate is only meaningful in the true case.

Basic Usage

Let's see a simple example where we define a type guard function and then use it to refine some values.

Defining a type guard function

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}

We have defined a data type AorB that is a disjoint union of two types A and B that each have a property type used as tag.

We have also written a user defined type guard function isA defined over objects of type AorB. This function returns true when the value of of the type property of its input is "A". Using the definitions of A and B, Flow can prove that when the value of type is "A" then the type of value will be A.

Using a type guard function to refine values

Functions that have a declared type guard can be used to refine values in conditionals. In the example above, we can use isA to refine a variable of type AorB to just A:

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}8
9function test(x: AorB) {10  if (isA(x)) {11    // `x` has now been refined to type A.12    // We can assign it variables of type A ...13    const y: A = x;14    // ...and access A's properties through `x`15    const stringData: string = x.data;16
17    // As a sanity check, the following assignment to B will error18    const error: B = x; // Errorincompatible-typeCannot assign x to error because properties data and type of A [1] are not exactly the same as those of B [2].19  }20}

In the then-branch of the conditional if (isA(x)), x will have the type A.

One-sided Type Guards

Note: This feature is available as of v0.237.0 when option one_sided_type_guards=true is set in the flowconfig. It is enabled by default as of v0.239.0.

In some cases we may want to declare that a type guard function only refines the then-branch of a conditional. Consider for example the function

1function isPositive(n: ?number): boolean {2  return n != null && n > 0;3}

If we declared n is number as the type guard of this function then in the following code we would be able to establish that n is null | void in the else-branch. This is not true, however, since n could just be a non-negative number:

1function isPositiveUnsound(n: ?number): n is number {2  return n != null && n > 0; // Errorincompatible-type-guardCannot return (n != null) && (n > 0) because the negation of the predicate encoded in this expression needs to completely refine away the guard type number [1]. Consider using a one-sided type-guard (implies x is T).3}4
5declare const n: ?number;6if (isPositiveUnsound(n)) {7  // n is number here8} else {9  // n would be inferred as null | void, but could actually be a non-negative number10}

One-sided type guards, which we annotate as implies param is PredicateType, let us specify that a predicate narrows the type in only the positive case. For example,

1function isPositive(n: ?number): implies n is number {2  return n != null && n > 0;3}

Now, we'll get the following behavior:

1function isPositive(n: ?number): implies n is number {2  return n != null && n > 0;3}4
5declare const n: ?number;6if (isPositive(n)) {7  n as number; // OK: n is number here8} else {9  n as ?number; // OK: n is still ?number10}

this Type guards

Note: This feature is available as of v0.261.0 when option this_type_guards=true is set in the flowconfig. It is enabled by default as of v0.269.0.

Sometimes, it is useful to declare a type predicate over the class instance on which a method is called. This can be done by adding this is Type as return annotation for this method. Calling this method in a conditional context will refine the instance to the type Type.

For example, consider the following class declarations:

1declare class Shape {2  isCircle(): this is Circle;3  isSquare(): this is Square;4}5
6declare class Circle {7  radius: number;8}9
10declare class Square {11  side: number;12}

The this type guard annotations allow us to refine a Shape-typed value to either Circle or Square:

1declare class Shape {2  isCircle(): this is Circle;3  isSquare(): this is Square;4}5declare class Circle { radius: number }6declare class Square { side: number }7
8function area(shape: Shape): number {9  if (shape.isCircle()) {10    // shape is now a Circle11    return Math.PI * shape.radius ** 2;12  } else if (shape.isSquare()) {13    // shape is now a Square14    return shape.side ** 2;15  } else {16    throw new Error('unknown shape');17  }18}

Note: The this type guard annotation is only allowed in the return annotation on non-static declare class and interface methods. For example the following are invalid forms:

declare class InvalidStatic {
static m(): this is D;
}

type InvalidTypeAlias = (x: unknown): this is A;

function invalidFunction(this: unknown): this is A;

class InvalidNonDeclareClass {
m(): this is B { return this instanceof B; }
}

Refine with Array.filter

Flow recognizes when you call filter on an array of type Array<T> with a callback function that holds a type guard with type (value: T) => value is S. It will use this to produce an output array of type Array<S>. Note that S needs to be a subtype of the type of the array element T.

For example

1type Success = Readonly<{type: 'success', value: 23}>;2type Error = Readonly<{type: 'error', error: string}>;3
4type Response =5  | Success6  | Error7
8function filterSuccess(response: Array<Response>): Array<Success> {9  return response.filter(10    (response): response is Success => response.type === 'success'11  );12}13
14function filterError1(response: Array<Response>): Array<Error> {15  const result = response.filter(16    (response): response is Success => response.type === 'success'17  );18  return result; // Errorincompatible-typeCannot return result because in array element: Success [1] is not exactly the same as Error [2].19}20
21function filterError2(response: Array<Response>): Array<Error> {22  const result = response.filter(23    (response): response is Error => response.type === 'success' // Errorincompatible-typeCannot return response.type === 'success' because property error is missing in object type [1] but exists in object type [2].incompatible-typeCannot return response.type === 'success' because property value is extra in object type [1] but missing in object type [2]. Exact objects do not accept extra props.incompatible-type-guardCannot return response.type === 'success' because in property type: string literal success [1] is incompatible with string literal error [2].incompatible-type-guardCannot return response.type === 'success' because the negation of the predicate encoded in this expression needs to completely refine away the guard type Error [1]. Consider using a one-sided type-guard (implies x is T).24  );25  return result;26}

In filterError1, filtering produces Array<Success> that is not compatible with the expected return type Array<Error>.

In filterError2, the predicate response.type === 'success' is used to refine Responses to Successs, not Errors.

Note that as of version 0.261 it is not necessary to provide a type guard annotation for the argument of .filter(). This will be inferred from the body of the arrow function:

1type Success = Readonly<{type: 'success', value: 23}>;2type Error = Readonly<{type: 'error', error: string}>;3type Response = Success | Error;4
5function filterSuccessShort(response: Array<Response>): Array<Success> {6  return response.filter(7    response => response.type === 'success'8  );9}

Defining Type Guard Functions

To ensure that refinement with type guard functions is sound, Flow runs a number of checks associated with these functions.

Predicate parameter is a regular parameter to the function

In a type guard annotation of the form parameter is Type, parameter needs to belong to the current function's parameter list.

1function missing(param: unknown): prop is number { // Errorfunction-predicateCannot find type guard parameter prop [1] in the parameters of this function (type).2  return typeof param === "number";3}

It cannot be a parameter bound in a destructuring pattern, or a rest parameter:

1function destructuring({prop}: {prop: unknown}): prop is number { // Errorfunction-predicateA type guard parameter prop [1] cannot reference pattern parameter prop [2].2  return typeof prop === "number";3}
1function rest(...value: Array<unknown>): value is Array<unknown> { // Errorfunction-predicateA type guard parameter value [1] cannot reference rest parameter value [2].2  return Array.isArray(value);3}

Predicate type is consistent with the parameter type

The type guard Type needs to be compatible with the type of the parameter. In other words, given a definition

function isT(x: ParamType): x is Type {
return ...
}

Flow will check that Type is a subtype of ParamType. So the following will be an error:

1function isNumber(x: string): x is number { // Errorincompatible-type-guardThe type predicate number [1] needs to be compatible with parameter x's type: number [1] is incompatible with string [2].2  return typeof x === "number";3}

Type guard function returns boolean

A type guard function needs to return a boolean expression. The following are invalid declarations:

1function isNumberNoReturn(x: string): x is string {} // Errorincompatible-typeCannot declare a type guard [1] for function [2] because boolean [1] is incompatible with implicitly-returned undefined.
1function nonMaybe<V extends {...}>(x: ?V): x is V {2  return x; // Errorincompatible-typeCannot return x because null or undefined [1] is incompatible with boolean [2].incompatible-typeCannot return x because object type [1] is incompatible with boolean [2].3}

A correct version of nonMaybe would be

1function nonMaybe<V extends {...}>(x: ?V): x is V {2  return !!x;3}

Predicate type is consistent with refined type

In addition to the above checks, Flow also ensures that the declared type guard is consistent with the check happening in the body of the function. To establish this it needs to guarantee two things:

  1. The type of the refined parameter at the return location after the predicate of the return expression has been applied is a subtype of the guard type. For example, the following definitions are correct:
1function numOrStr(x: unknown): x is number | string {2  return (typeof x === "number" || typeof x === "string");3}4
5function numOrStrWithException(x: unknown): x is number | string {6  if (typeof x === "number") {7    return true;8  } else {9    if (typeof x === "string") {10        return true;11    } else {12        throw new Error("");13    }14  }15}

But in the following Flow will raise errors:

1function numOrStrError(x: unknown): x is number | string {2  return (typeof x === "number" || typeof x === "boolean"); // Errorincompatible-type-guardCannot return ((typeof x) === "number") || ((typeof x) === "boolean") because: Either boolean [1] is incompatible with number [2]. Or boolean [1] is incompatible with string [3].incompatible-type-guardCannot return ((typeof x) === "number") || ((typeof x) === "boolean") because the negation of the predicate encoded in this expression needs to completely refine away the guard type union type [1]. Consider using a one-sided type-guard (implies x is T).3}
  1. Type guard functions can be used to refine the else-branch of conditionals. For example,
1function isNumber(x: unknown): x is number {2  return typeof x === "number";3}4
5declare const value: number | string;6if (isNumber(value)) {7  value as number; // okay8} else {9  value as string; // also okay10}

Therefore, the inverse form of the first requirement also needs to hold. Specifically, if we negate the predicate encoded in the function, and use it to refine the input, then the result must not overlap with the type guard at all. This condition is equivalent to checking that the type guard refined with the negation of the function predicate is a subtype of empty. For example the following raises an error:

1function isPosNum(x: unknown): x is number {2    return typeof x === 'number' && x > 0; // Errorincompatible-type-guardCannot return ((typeof x) === 'number') && (x > 0) because the negation of the predicate encoded in this expression needs to completely refine away the guard type number [1]. Consider using a one-sided type-guard (implies x is T).3}

This is because the negation of the predicate of isPosNum is "x is not a number or x<=0". This predicate is equivalent to the empty predicate and does not refine the input type it is applied to.

If you're seeing errors related to this check, consider using a one-sided type guard (write implies x is T). Ones-sided type guards do not require this check, since they do not refine the else-branch of conditionals.

Note: This check only happens when one_sided_type_guards=true is set in the flowconfig. It happens by default as of v0.239.0.

  1. The parameter that is refined cannot be reassigned in the body of the type guard function. Therefore the following are errors:
1function isNumberError1(x: unknown): x is number {2  x = 1;3  return typeof x === "number"; // Errorfunction-predicateCannot use type guard parameter x [1] because at this return point it is written to in [2].4}
1function isNumberError2(x: unknown): x is number { // Errorfunction-predicateCannot use type guard parameter x, because x [1] is reassigned in [2].2  function foo() {3    x = 1;4  }5  foo();6  return typeof x === "number";7}

Type Guards with Generic Union Members

Type guard functions can fail Flow's consistency checks when a union member uses a generic type parameter in the property you refine on. Flow cannot prove the refined type is compatible with the guard type because the generic could overlap with the guard's literal:

1type Known = {tag: 'known', value: string};2type Dynamic<T> = {tag: T, data: number};3type Item<T> = Known | Dynamic<T>;4
5function isKnown<T>(x: Item<T>): x is Known {6  return x.tag === 'known'; // ERRORincompatible-typeCannot return x.tag === 'known' because property data is extra in Dynamic [1] but missing in Known [2]. Exact objects do not accept extra props.incompatible-typeCannot return x.tag === 'known' because property value is missing in Dynamic [1] but exists in Known [2].incompatible-type-guardCannot return x.tag === 'known' because in property tag: T [1] is not exactly the same as "known" [2].7}

The error occurs because T could be 'known', making it impossible to distinguish Known from Dynamic<T> by checking tag alone.

To fix this, add a separate literal discriminant property that does not use the generic. This gives Flow a concrete tag to refine on:

1type Known = {type: 'known', tag: 'known', value: string};2type Dynamic<T> = {type: 'dynamic', tag: T, data: number};3type Item<T> = Known | Dynamic<T>;4
5function isKnown<T>(x: Item<T>): x is Known {6  return x.type === 'known';7}

Adoption

To use type guards, you need to upgrade your infrastructure so that it supports the syntax:

  • flow and flow-parser:
    • 0.209.1. Between v0.209.1 to v0.211.1, you need to explicitly enable it in your .flowconfig, under the [options] heading, add type_guards=true.
    • One-sided type guards are available as of version 0.237.0 with the option one_sided_type_guards=true, and are enabled by default as of v0.239.0.
    • this type guards are available as of version 0.261.0 with the option this_type_guards=true, and are enabled by default as of v0.269.0.
  • prettier: 3. this type guards require version 3.5 or later. See these instructions for installing prettier.
  • babel with babel-plugin-syntax-hermes-parser. See our Babel guide for setup instructions.
    • this type guards require hermes-parser version 0.26 or later.
  • eslint with hermes-eslint. See our ESLint guide for setup instructions.

See Also

  • Refinements — built-in type narrowing with typeof, instanceof, and equality checks
  • Unions — type guards are commonly used to narrow union types
  • Functions — type guard syntax builds on function return type annotations