env.dev

Constants in JavaScript / TypeScript

const, Object.freeze, and built-in constants

The const Keyword

JavaScript introduced const in ES2015 (ES6) as a block-scoped declaration that prevents reassignment. A const binding must be initialized at the point of declaration and cannot be reassigned afterward. This is the primary mechanism for declaring constants in JavaScript and TypeScript.

javascript
const MAX_RETRIES = 3;
const API_BASE_URL = 'https://api.example.com/v1';
const TIMEOUT_MS = 30_000;

// MAX_RETRIES = 5; // TypeError: Assignment to constant variable

It is important to understand that const prevents reassignment of the binding, not mutation of the value. A const object can still have its properties changed, a const array can be pushed to, and a const Map can have entries added. For true immutability of compound values, you need Object.freeze() or TypeScript's as const.

Object.freeze and Deep Immutability

Object.freeze() makes an object shallowly immutable — its own properties cannot be added, removed, or reassigned. In strict mode, attempts to modify a frozen object throw a TypeError. In sloppy mode, modifications are silently ignored.

javascript
const CONFIG = Object.freeze({
  maxUploadSize: 10 * 1024 * 1024,
  allowedFormats: Object.freeze(['png', 'jpg', 'webp']),
  api: Object.freeze({
    baseUrl: 'https://api.example.com',
    timeout: 5000,
  }),
});

// CONFIG.maxUploadSize = 0; // TypeError in strict mode

Note that Object.freeze() is shallow. Nested objects must be frozen individually, or you can write a recursive deep-freeze utility. In practice, TypeScript's as const assertion is often preferred because it provides compile-time immutability checking without the runtime overhead of freezing.

TypeScript as const

The as const assertion tells TypeScript to infer the narrowest possible type for a literal expression. For objects, every property becomes readonly with a literal type. For arrays, the result is a readonly tuple. This is the idiomatic way to define constant data structures in TypeScript.

typescript
const HTTP_STATUS = {
  Ok: 200,
  Created: 201,
  BadRequest: 400,
  NotFound: 404,
  ServerError: 500,
} as const;

type HttpStatus = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];
// 200 | 201 | 400 | 404 | 500

const DIRECTIONS = ['north', 'south', 'east', 'west'] as const;
type Direction = typeof DIRECTIONS[number];
// 'north' | 'south' | 'east' | 'west'

Built-in Constants

JavaScript provides a rich set of built-in constants across its standard library. The Number object exposes numeric limits and special values. The Math object provides mathematical constants. Infinity, NaN, and undefined are global constants.

javascript
// Number constants
Number.MAX_SAFE_INTEGER  // 9007199254740991 (2^53 - 1)
Number.MIN_SAFE_INTEGER  // -9007199254740991
Number.MAX_VALUE         // ~1.8e308
Number.MIN_VALUE         // ~5e-324 (smallest positive)
Number.EPSILON           // ~2.2e-16
Number.POSITIVE_INFINITY // Infinity
Number.NEGATIVE_INFINITY // -Infinity
Number.NaN               // NaN

// Math constants
Math.PI    // 3.141592653589793
Math.E     // 2.718281828459045
Math.LN2   // 0.6931471805599453
Math.LN10  // 2.302585092994046
Math.SQRT2 // 1.4142135623730951

Common Patterns

  • Use const by default for all declarations. Only use let when you genuinely need to reassign the variable. Never use var.
  • Define shared constants in a dedicated module and import them where needed rather than repeating literal values across files.
  • Use numeric separators for readability: 1_000_000 instead of 1000000, 0xFF_FF instead of 0xFFFF.
  • Prefer as const over TypeScript enum for better tree shaking and no runtime overhead.
  • Use satisfies with as const to validate constant shapes while preserving literal types: const x = { ... } as const satisfies Config.