Enums vs. Const Assertions (as const)
Quick Overview
A numeric `enum` emits a real object and a reverse mapping into the bundle. `as const` produces the same literal union with zero runtime output, which is why it is now the usual choice.
Deep Dive
1. Constant Value Groups
In software engineering, you often need to group named constant values together (such as HTTP status codes, user roles, or payment states).
2. TypeScript Enums
TypeScript provides the enum keyword for defining named constants:
// String Enum
enum UserRole {
Admin = "ADMIN",
Editor = "EDITOR",
Viewer = "VIEWER",
}
function checkAccess(role: UserRole) {
if (role === UserRole.Admin) {
console.log("Full access granted.");
}
}
3. Modern Alternative: as const (Const Assertions)
While Enums generate extra JavaScript code during compilation, Const Assertions (as const) create zero-overhead runtime objects with narrow literal types:
// Plain JavaScript object with 'as const' assertion
const USER_ROLES = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER",
} as const;
// Extract union type from the object values
type Role = typeof USER_ROLES[keyof typeof USER_ROLES]; // "ADMIN" | "EDITOR" | "VIEWER"
function setRole(r: Role) {
console.log("Setting role to:", r);
}
setRole(USER_ROLES.Admin); // Valid!
4. Feature Comparison Matrix
| Feature | Enum | Const Assertion (as const) |
|---|---|---|
| Runtime JS Output | Generates IIFE object code | Generates plain JS object |
| Type Narrowing | Nominal Enum type | Literal Union type |
| Tree-Shaking | Harder for bundlers | 100% Tree-shakable |
5. Common Mistakes
- Numeric Enum Pitfalls: Numeric enums (
enum Status { Pending, Active }) generate reverse mapping objects at runtime, making them accept arbitrary numbers without type warnings! Default to string enums oras const.
6. Next Steps
Use as const for clean, tree-shakable constants. Next, let's explore Literal Types and Template Literal Types!
Next Up in TypeScript & Type System Mechanics
TypeScript Primitives & Explicit Type Annotations
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
Type Inference & Literal Types (const vs. let)
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
Arrays, Tuples & Readonly Modifiers
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.
