# TypeScript Const Assertion: How `as const` Narrows Types Beyond Regular `const` Declarations

> Master TypeScript const assertion with `as const`. Discover how it narrows types beyond regular const declarations, preventing reassignment and enhancing type inference for more robust code.

- Repository: [Microsoft/TypeScript](https://github.com/microsoft/typescript)
- Tags: deep-dive
- Published: 2026-02-16

---

**TypeScript's `as const` assertion narrows expressions to readonly literal types, while a regular `const` declaration only prevents variable reassignment without affecting type inference.**

The typescript const assertion is a compile-time feature in the Microsoft TypeScript compiler that transforms inferred types into their most specific literal forms. Unlike a standard `const` declaration that merely guards against rebinding, `as const` produces immutable readonly tuples and objects, enabling precise type safety for discriminated unions and API contracts without verbose manual annotations.

## What Is a TypeScript Const Assertion?

The `as const` syntax, introduced in TypeScript 3.4, is a type assertion that instructs the compiler to infer the narrowest possible literal type for an expression. When applied to an array or object literal, it produces a **readonly tuple** or **readonly object type** where all properties are marked as `readonly` and all string or number literals retain their specific values rather than widening to `string` or `number`.

Consider the following comparison:

```typescript
// Widened type: string[]
const colors = ["red", "green"];

// Narrowed type: readonly ["red", "green"]
const colorsNarrow = ["red", "green"] as const;

```

In the first example, TypeScript infers `string[]`, allowing the array to be modified and its elements to be any string. In the second, the typescript const assertion locks the type to a tuple of specific literals that cannot be modified structurally.

## How `as const` Differs from Regular `const` Declarations

A regular `const` declaration and the `as const` assertion operate at different levels of the type system. The former controls **binding mutability**, while the latter controls **type narrowing** and **structural immutability**.

| Aspect | `const` Declaration | `as const` Assertion |
|--------|--------------------|----------------------|
| **Binding mutability** | Prevents the variable from being reassigned. | No effect on the binding; the expression can be used inline anywhere. |
| **Type inference** | Infers the widened type of the initializer (e.g., `string[]`). | Narrows to the most specific literal type and marks it `readonly` (e.g., `readonly ["red", "green"]`). |
| **Readonly guarantee** | Does not make object or array properties readonly. | Implicitly adds `readonly` to all properties recursively. |
| **Use case** | When you need a stable variable reference but mutable data. | When you need exact literal shapes for discriminated unions or immutable API contracts. |

### Binding Mutability vs. Type Narrowing

When you declare a variable with `const`, you create an immutable binding between an identifier and a value. However, if that value is an object or array, its contents remain mutable:

```typescript
const config = { host: "localhost", port: 3000 };
config.port = 8080; // Valid: the object is mutable
// config = {}; // Error: cannot reassign the binding

```

By contrast, the typescript const assertion affects the type system directly. It tells the compiler to treat the expression as a frozen literal value, creating a readonly tuple or object type that cannot be modified structurally:

```typescript
const config = { host: "localhost", port: 3000 } as const;
// config.port = 8080; // Error: property is readonly

```

### Readonly Guarantees

The `as const` assertion recursively applies `readonly` to all properties. According to the TypeScript compiler implementation in [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts), when a const assertion is present, the inferred type is transformed into a readonly literal type rather than a widened mutable type.

## How the TypeScript Compiler Implements `as const`

The implementation of the typescript const assertion spans the compiler's utility functions, type checker, and language service codefixes. Understanding these internals clarifies why `as const` produces readonly literal types.

### Detecting Const Assertions in [`utilities.ts`](https://github.com/microsoft/TypeScript/blob/main/utilities.ts)

The compiler identifies `as const` syntax through the `isConstAssertion` utility function defined in [`src/compiler/utilities.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/utilities.ts) at line 11449. This function checks whether a type assertion node uses the `const` keyword:

```typescript
// src/compiler/utilities.ts#L11449
function isConstAssertion(node: TypeAssertion | ExpressionWithTypeArguments): boolean {
    // Implementation checks for 'const' type assertion
}

```

### Type Checking and Narrowing in [`checker.ts`](https://github.com/microsoft/TypeScript/blob/main/checker.ts)

The core type transformation logic resides in [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts). At line 38070, the checker validates that the operand of a const assertion is a valid const-assertion argument—specifically, literals, array literals, or object literals.

Once validated, the type inference engine transforms the widened type into a readonly literal type. At line 17373 in [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts), the compiler constructs the readonly tuple or object type, recursively applying `readonly` modifiers to ensure deep immutability:

```typescript
// Conceptual representation of checker.ts logic
if (isConstAssertion(node)) {
    return getReadonlyLiteralType(expressionType);
}

```

### Codefix Integration

The TypeScript language service leverages const assertion detection for automated refactoring. In [`src/services/codefixes/fixMissingTypeAnnotationOnExports.ts`](https://github.com/microsoft/TypeScript/blob/main/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts) at line 451, the `isConstAssertion` utility is referenced to determine whether a variable already has a const assertion, which affects how missing type annotations are inferred and fixed.

## Practical Examples of TypeScript Const Assertion

Beyond theoretical differences, the typescript const assertion solves specific problems in real-world codebases, particularly when working with discriminated unions and API contracts.

### Creating Readonly Tuples for Discriminated Unions

Discriminated unions rely on literal types to distinguish between variants. Without `as const`, array literals widen to mutable arrays of primitive types, breaking exhaustiveness checking:

```typescript
// Without as const: type is string[]
const STATUS_CODES = ["pending", "success", "error"];

type Status = typeof STATUS_CODES[number]; // string (too wide)

```

Using the typescript const assertion creates a readonly tuple that preserves literal types for proper union discrimination:

```typescript
// With as const: type is readonly ["pending", "success", "error"]
const STATUS_CODES = ["pending", "success", "error"] as const;

type Status = typeof STATUS_CODES[number]; // "pending" | "success" | "error"

function handleStatus(status: Status) {
    switch (status) {
        case "pending": return "Loading...";
        case "success": return "Done!";
        case "error": return "Failed!";
        default: 
            // TypeScript knows this is unreachable
            const _exhaustive: never = status;
            return _exhaustive;
    }
}

```

### Preventing Type Widening in API Contracts

When defining configuration objects or API endpoints, `as const` ensures that string literals remain specific rather than widening to `string`. This prevents accidental assignment of invalid values:

```typescript
const API_ENDPOINTS = {
    users: "/api/users",
    posts: "/api/posts"
} as const;

// Type is readonly { readonly users: "/api/users"; readonly posts: "/api/posts"; }
// Not { users: string; posts: string; }

function fetchData(endpoint: typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS]) {
    return fetch(endpoint);
}

fetchData(API_ENDPOINTS.users); // Valid
// fetchData("/api/comments"); // Error: Argument of type '"/api/comments"' is not assignable

```

## Summary

The typescript const assertion provides type-level immutability and literal type preservation that regular `const` declarations cannot achieve:

- **Binding vs. Type Level**: Regular `const` prevents variable reassignment; `as const` narrows types to readonly literal forms.
- **Readonly Deep Freeze**: The assertion recursively applies `readonly` to all properties, creating immutable tuples and objects.
- **Compiler Implementation**: The TypeScript compiler detects `as const` via `isConstAssertion` in [`src/compiler/utilities.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/utilities.ts) and transforms types in [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts) at lines 38070 and 17373.
- **Practical Use Cases**: Essential for discriminated unions, exhaustive switch statements, and API contracts requiring exact literal types.

## Frequently Asked Questions

### What is the difference between `const` and `as const` in TypeScript?

A regular `const` declaration prevents the variable binding from being reassigned to a new value, but the inferred type remains widened (e.g., `string[]` for an array of strings). In contrast, `as const` is a type assertion that narrows the expression to its most specific literal type and marks it as readonly (e.g., `readonly ["red", "green"]`), preventing structural modifications.

### When should I use the TypeScript const assertion instead of a type annotation?

Use `as const` when you want the compiler to infer the exact literal types from an initializer without manually writing verbose type annotations. This is particularly useful for configuration objects, route definitions, or status codes where you need specific string literals for discriminated unions. If you need mutable types or plan to reassign the variable, stick with regular `const` or `let` with explicit type annotations.

### Does `as const` affect runtime behavior or only compile-time types?

The `as const` assertion is purely a compile-time construct that affects type inference and checking. It generates no additional JavaScript code at runtime; the emitted code is identical to what would be produced without the assertion. The readonly guarantees exist only in the type system, though they can prevent accidental mutations during development when combined with TypeScript's strict type checking.

### How does the TypeScript compiler recognize `as const` internally?

According to the Microsoft TypeScript source code, the compiler identifies const assertions through the `isConstAssertion` utility function located in [`src/compiler/utilities.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/utilities.ts) at line 11449. During type checking, the compiler validates const assertion arguments in [`src/compiler/checker.ts`](https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts) at line 38070 and transforms the inferred type into a readonly literal type at line 17373. The language service also uses this detection for automated codefixes in [`src/services/codefixes/fixMissingTypeAnnotationOnExports.ts`](https://github.com/microsoft/TypeScript/blob/main/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts).