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.
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 variableIt 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.
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 modeNote 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.
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.
// 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.4142135623730951Common Patterns
- Use
constby default for all declarations. Only useletwhen you genuinely need to reassign the variable. Never usevar. - 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_000instead of1000000,0xFF_FFinstead of0xFFFF. - Prefer
as constover TypeScriptenumfor better tree shaking and no runtime overhead. - Use
satisfieswithas constto validate constant shapes while preserving literal types:const x = { ... } as const satisfies Config.