Skip to main content

Const Expressions

The as const modifier tells Flow to infer the narrowest possible type for a literal expression — literal types for primitives and read-only types for containers.

1const x = 42 as const;           // type is 42, not number2const a = [1, 2] as const;       // type is Readonly<[1, 2]>, not Array<number>3const o = {x: 1} as const;       // type is Readonly<{x: 1}>, not {x: number}
Try

When to use this

Use as const when you need Flow to preserve exact literal values and read-only structure — for example, keeping specific string values in a configuration object or ensuring an array is treated as a fixed-length tuple. If you only need an array or object literal to be read-only without narrowing to literal types, use the Readonly utility type instead.

Typing for Const Expressions

The inferred type of const-expressions is the singleton type for primitive values and the read-only versions for container types. Array literals are inferred as tuple types.

Here are some examples of primitive values:

142 as const; // inferred type is 422
3"hello" as const; // inferred type is "hello"
Try

Containers become read-only and the modifier is applied deeply

1const o1 = {f: 42} as const; // {readonly f: 42}2
3const t1 = [42, "hello"] as const; // Readonly<[42, "hello"]>4
5const o2 = {f: {g: 42}} as const; // {readonly f: {readonly g: 42}}
Try

Note that the effect of the modifier does not persist through variables. For example in

1const nonConstObject = { g: 42 };2const constObject = { f: nonConstObject } as const;
Try

the type of nonConstObject will be {g: number} and the type of constObject will be {readonly f: {g: number}}. In other words, only the top-level property f will be read-only.

Finally, as const works on literals directly, and on const variables that are initialized with a primitive literal. Applying it to other expressions — for instance a value read from a let binding — is an error:

1let x = 1;2const y = x as const; // Errorunsupported-syntaxThe as const assertion can only be used on string, numeric, boolean, object, or array literals, or const-variables initialized with primitive literals.
Try

Typical const-expression example

A common pattern where const-expressions are useful is in enum-like structures that are not expected to be mutated. For example

1export const STATUS = {2  INIT: 'INIT',3  LOADING: 'LOADING',4  SUCCESS: 'SUCCESS',5  ERROR: 'ERROR',6} as const;
Try

The type of STATUS.INIT is "INIT", the type of STATUS.LOADING is "LOADING" and so on.

With this definition it is also possible to effectively lift the values of the various fields to type annotations. For example:

1const STATUS = {2  INIT: 'INIT',3  LOADING: 'LOADING',4  SUCCESS: 'SUCCESS',5  ERROR: 'ERROR',6} as const;7
8type State =9  | { readonly kind: typeof STATUS.INIT; }10  | { readonly kind: typeof STATUS.LOADING; progress: number; }11  | { readonly kind: typeof STATUS.SUCCESS; result: string; }12  | { readonly kind: typeof STATUS.ERROR; msg: string; };
Try

Without the use of as const the type typeof STATUS.INIT would be string, which would make it unsuitable as a distinguishing tag in a disjoint union.

Adoption of as const syntax

To use the as const syntax, you need to upgrade your infrastructure:

const Type Parameters

Sometimes it is useful to specify that an argument to a function is always expected to be a const-expression. In such cases, you can annotate the type parameter with the const modifier. We refer to these type parameters as const-type parameters.

When are const type parameters useful?

One example is when you want to enforce that all arguments passed to a function foo with signature

declare function foo<X>(x: X): X;

need to be treated as const-expressions. One way to support this is by always calling foo with as const on its argument:

1declare function foo<X>(x: X): X;2
3const x1 = foo({ f: 42 } as const);4const x2 = foo([42, "hello"] as const);
Try

The variables x1 and x2 will have the types {readonly f: 42} and Readonly<[42, "hello"]>, respectively.

To avoid repeating and potentially forgetting to pass as const, you can use the const modifier on type parameter X:

1declare function constFoo<const X>(x: X): X;2
3const y1 = constFoo({ f: 42 });4const y2 = constFoo([42, "hello"]);
Try

The variables y1 and y2 will have the same type as x1 and x2, respectively.

Adoption of const type parameter syntax

To use the const type parameter syntax, you need to upgrade your infrastructure:

See Also

  • Literal Types — the literal types that as const infers for primitive values
  • Tuplesas const on arrays produces read-only tuple types
  • Type Casting — the as keyword for general type assertions (distinct from as const)
  • Genericsconst type parameters for enforcing const-expression inference on function arguments
  • Varianceas const produces read-only (covariant) properties