🔷 TypeScript Q73 / 101

Explain const assertions.

AI-Powered Answer ✓ Answered

TypeScript's `const` assertion (`as const`) is a powerful feature that allows you to instruct the TypeScript compiler to infer the narrowest possible type for a given literal expression. This typically means converting mutable types into readonly, and widening types into literal types or tuple types.

What are `const` Assertions?

When you declare a variable with const in JavaScript, it means the variable's reference cannot be reassigned. However, in TypeScript, the *type* of the value assigned to a const variable can still be wide. For example, a const array can still be modified, and an object's properties can still be changed. const assertions explicitly tell TypeScript to infer types as literally as possible, making values effectively immutable from a type perspective.

Specifically, as const applies the following transformations:

  • Literal Types: String, number, boolean literals infer their exact literal type (e.g., "hello" instead of string, 123 instead of number).
  • Readonly Properties: Properties of objects become readonly.
  • Readonly Tuples: Arrays become readonly tuples (e.g., readonly [1, 2, 3] instead of number[]).

Why Use `const` Assertions?

  • Improved Type Inference: Get more precise types, especially useful for configuration objects, state definitions, or API constants.
  • Enhanced Immutability: Enforce immutability at compile-time for objects and arrays, preventing accidental modifications and leading to more robust code.
  • Better Auto-Completion: IDEs can provide more specific suggestions based on literal types.
  • Cleaner Code: Avoid verbose type annotations when you want a literal type or a readonly structure.

Syntax and Examples

The syntax is simple: append as const to the end of a literal expression (object, array, or primitive).

Example 1: Literal Types and Readonly Object Properties

typescript
const CONFIG_WITHOUT_CONST_ASSERTION = {
  debug: true,
  env: "development"
};
// Type of CONFIG_WITHOUT_CONST_ASSERTION.debug is 'boolean'
// Type of CONFIG_WITHOUT_CONST_ASSERTION.env is 'string'
CONFIG_WITHOUT_CONST_ASSERTION.debug = false; // Valid, type is boolean

const CONFIG_WITH_CONST_ASSERTION = {
  debug: true,
  env: "development"
} as const;
// Type of CONFIG_WITH_CONST_ASSERTION.debug is 'true' (literal type)
// Type of CONFIG_WITH_CONST_ASSERTION.env is '"development"' (literal type)
// CONFIG_WITH_CONST_ASSERTION.debug = false; // Error: Cannot assign to 'debug' because it is a read-only property.

Example 2: Readonly Tuples

typescript
const MUTABLE_ARRAY = [1, "hello", true];
// Type is (string | number | boolean)[]
MUTABLE_ARRAY.push(false); // Valid
MUTABLE_ARRAY[0] = 5;      // Valid

const READONLY_TUPLE = [1, "hello", true] as const;
// Type is readonly [1, "hello", true]
// READONLY_TUPLE.push(false); // Error: Property 'push' does not exist on type 'readonly [1, "hello", true]'.
// READONLY_TUPLE[0] = 5;      // Error: Cannot assign to '0' because it is a read-only property.

Example 3: Nested Structures

as const applies recursively, making nested objects and arrays also readonly and converting their primitive values to literal types.

typescript
const DEEP_CONFIG = {
  user: {
    id: 123,
    name: "Alice"
  },
  roles: ["admin", "user"],
  status: "active"
} as const;

// DEEP_CONFIG.user.id is type 123 (literal)
// DEEP_CONFIG.user.name is type "Alice" (literal)
// DEEP_CONFIG.roles is type readonly ["admin", "user"] (readonly tuple)
// DEEP_CONFIG.status is type "active" (literal)

// DEEP_CONFIG.user.id = 456; // Error: Cannot assign to 'id' because it is a read-only property.
// DEEP_CONFIG.roles.push("guest"); // Error: Property 'push' does not exist on type 'readonly ["admin", "user"]'.

Important Considerations

  • No Runtime Effect: as const is a compile-time construct only. It does not alter the JavaScript output or introduce any runtime overhead. The actual immutability at runtime depends on how the JavaScript object is used.
  • Use with Caution: While powerful, overusing as const can sometimes make types too restrictive, especially when interacting with external libraries that expect more flexible types (e.g., string instead of a specific literal string).
  • Not for Function Arguments: as const is an assertion on a literal. You cannot apply as const to a function parameter to make it readonly or infer literal types, as the parameter's value is not a literal known at the declaration site.