Literal Types & const Assertions

Difficulty: Intermediate

Question

What are literal types in TypeScript? How does 'as const' work and when should you use it?

Answer

Literal types represent exact values rather than broad categories. Instead of 'string', a literal type is a specific string like 'hello'. TypeScript automatically infers literal types for const declarations.

'as const' (const assertion) tells TypeScript to infer the narrowest possible type for an expression: - Strings/numbers become literal types instead of string/number - Arrays become readonly tuples instead of mutable arrays - Objects get readonly properties with literal types

This is incredibly useful for configuration objects, action types, and anywhere you want TypeScript to track exact values.

Code examples

Literal Types

// String literal types
type Direction = 'north' | 'south' | 'east' | 'west';
let dir: Direction = 'north'; // OK
// dir = 'up'; // Error: Type '"up"' is not assignable to type 'Direction'

// Numeric literal types
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): DiceRoll {
  return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}

// Boolean literal types
type True = true;
type False = false;

// let vs const inference
let mutable = 'hello';    // type: string (wide)
const immutable = 'hello'; // type: 'hello' (literal)

// Literal types in function parameters
function setAlignment(align: 'left' | 'center' | 'right'): void {
  document.body.style.textAlign = align;
}
setAlignment('center'); // OK
// setAlignment('top');  // Error

Literal types restrict values to exact matches. They are the foundation of discriminated unions and enable powerful pattern matching.

const Assertions (as const)

// Without as const
const config = {
  endpoint: 'https://api.example.com',
  retries: 3,
  methods: ['GET', 'POST'],
};
// type: { endpoint: string; retries: number; methods: string[] }

// With as const
const configConst = {
  endpoint: 'https://api.example.com',
  retries: 3,
  methods: ['GET', 'POST'],
} as const;
// type: {
//   readonly endpoint: 'https://api.example.com';
//   readonly retries: 3;
//   readonly methods: readonly ['GET', 'POST'];
// }

// Useful for action types (Redux-style)
const ACTIONS = {
  INCREMENT: 'INCREMENT',
  DECREMENT: 'DECREMENT',
  RESET: 'RESET',
} as const;

type ActionType = typeof ACTIONS[keyof typeof ACTIONS];
// type: 'INCREMENT' | 'DECREMENT' | 'RESET'

// Without as const, ActionType would just be 'string'

as const freezes values to their literal types and makes everything readonly. It's essential for deriving union types from objects.

Practical Patterns with as const

// Route definitions
const ROUTES = {
  home: '/',
  about: '/about',
  users: '/users',
  userDetail: '/users/:id',
} as const;

type Route = typeof ROUTES[keyof typeof ROUTES];
// '/' | '/about' | '/users' | '/users/:id'

function navigate(route: Route): void {
  window.location.href = route;
}
navigate(ROUTES.home); // OK
// navigate('/random');  // Error

// Tuple from as const
const rgb = [255, 128, 0] as const;
// type: readonly [255, 128, 0] (not number[])

// Enum-like pattern without enum keyword
const Status = {
  Active: 'active',
  Inactive: 'inactive',
  Pending: 'pending',
} as const;

type Status = typeof Status[keyof typeof Status];
// 'active' | 'inactive' | 'pending'

function setStatus(status: Status): void {
  console.log(`Setting status to: ${status}`);
}
setStatus(Status.Active); // OK

The 'as const + typeof + keyof' pattern is a popular alternative to enums. It provides the same type safety with less runtime overhead.

Key points

Concepts covered

Literal Types, const Assertions, as const, Template Literals, Narrowing