Enums
In TypeScript, Enums are a unique feature that allows for the definition of named constants. They offer a convenient way to group related values together, making the code more readable and reducing the chance of errors.
Why Use Enums?
- Readability: Enums make the code more expressive by replacing magic numbers or strings with meaningful names.
- Safety: Reduce bugs by ensuring only valid values are used.
- Documentation: Enums serve as in-code documentation, indicating the possible values for a variable.
Numeric Enums
The most common type of enum assigns numbers to its members:
By default, enums start numbering their members from 0. You can change the starting number, as shown above, and subsequent members will increase by 1.
String Enums
String enums allow each member to have a string value:
Practical Example: Handling User Roles
Consider a system where users have different roles, and you want to assign specific permissions based on these roles.
enum UserRole {
ADMIN = "ADMIN",
EDITOR = "EDITOR",
VIEWER = "VIEWER"
}
function assignPermission(role: UserRole): string {
switch (role) {
case UserRole.ADMIN:
return "Full access granted.";
case UserRole.EDITOR:
return "Edit access granted.";
case UserRole.VIEWER:
return "View access granted.";
default:
return "No specific access granted.";
}
}
// Usage
console.log(assignPermission(UserRole.ADMIN)); // Full access granted.
console.log(assignPermission(UserRole.VIEWER)); // View access granted.
Modern Alternatives to Enums
While enums are useful, modern TypeScript offers alternatives that provide better tree-shaking and type safety:
String Literal Unions
// Instead of enum
type UserRole = "ADMIN" | "EDITOR" | "VIEWER";
// With const assertion for autocomplete
const USER_ROLES = {
ADMIN: "ADMIN",
EDITOR: "EDITOR",
VIEWER: "VIEWER"
} as const;
Benefits of Modern Alternatives
- Better Tree-shaking: Unused values can be eliminated from the bundle
- Runtime Safety: Values exist at runtime for reflection
- No TypeScript-specific syntax: Works in plain JavaScript
- Better with Module Bundlers: No special handling required
Advanced Topics
The following sections cover more advanced enum features that are useful for specific scenarios but may not be needed in everyday development.
Heterogeneous Enums
While not recommended, it’s possible to mix string and numeric values:
Best Practice
Avoid mixing string and numeric values in the same enum. Use either all strings or all numbers for consistency and clarity.
Computed and Constant Members
Enum members can be either constant or computed. Understanding the difference is important for how enums behave at compile time and runtime.
Constant Members
Constant members have values that can be fully evaluated at compile time. These include:
- Literal values (numbers or strings)
- References to other constant enum members
- Simple expressions using constant members
enum Status {
Pending = 0, // Literal value
Active = 1, // Literal value
Inactive = Active + 1, // Simple expression (evaluates to 2)
Archived = 10,
Deleted = Archived + 1 // Simple expression (evaluates to 11)
}
console.log(Status.Inactive); // 2
console.log(Status.Deleted); // 11
Computed Members
Computed members have values determined at runtime. These are typically function calls or complex expressions:
enum TimeBased {
Now = Date.now(), // Computed: function call
Random = Math.random() * 100 // Computed: expression with function
}
console.log(TimeBased.Now); // e.g., 1699545600000
console.log(TimeBased.Random); // e.g., 42.7
Mixing Constant and Computed Members
You can mix both types, but be careful with the order:
enum MixedEnum {
A = 1, // Constant
B, // Constant (auto-increments to 2)
C = getValue(), // Computed
// D, // ❌ ERROR: Enum member must have initializer
D = 10, // ✅ Must specify value after computed member
E // ✅ OK: auto-increments to 11
}
function getValue(): number {
return 5;
}
Important Rule
After a computed member, you must explicitly initialize the next member. TypeScript cannot auto-increment from a computed value.
Practical Example: File Permissions
A common use case is bitwise operations for flags or permissions:
enum FileAccess {
None = 0, // Constant: 0
Read = 1 << 1, // Constant: 2 (bit shift)
Write = 1 << 2, // Constant: 4 (bit shift)
Execute = 1 << 3, // Constant: 8 (bit shift)
ReadWrite = Read | Write, // Constant: 6 (2 | 4)
All = Read | Write | Execute // Constant: 14 (2 | 4 | 8)
}
console.log(FileAccess.Read); // 2
console.log(FileAccess.ReadWrite); // 6
console.log(FileAccess.All); // 14
// Check permissions using bitwise AND
function hasPermission(access: FileAccess, permission: FileAccess): boolean {
return (access & permission) === permission;
}
console.log(hasPermission(FileAccess.ReadWrite, FileAccess.Read)); // true
console.log(hasPermission(FileAccess.ReadWrite, FileAccess.Execute)); // false
About Bitwise Operations
1 << 1means “shift bit 1 left by 1 position” = binary10= decimal21 << 2means “shift bit 1 left by 2 positions” = binary100= decimal4|is the bitwise OR operator, combining bits&is the bitwise AND operator, checking if bits are set
This is useful for permission systems where you can combine multiple flags.
Reverse Mapping
Numeric enums support reverse mapping, meaning you can access the enum name from its value:
enum Direction {
Up = 1,
Down,
Left,
Right
}
console.log(Direction[1]); // "Up"
console.log(Direction[2]); // "Down"
console.log(Direction.Up); // 1
// Note: String enums do NOT support reverse mapping
enum Color {
Red = "red",
Blue = "blue"
}
console.log(Color.Red); // "red"
console.log(Color["red"]); // undefined (no reverse mapping)
Const Enums
To ensure optimal performance and remove the runtime impact, you can use const enums. The compiler will inline any usage of the enum:
Const Enums Considerations
Const enums are inlined at compile time, which can cause issues with:
- Module bundlers that don’t understand TypeScript
- Runtime reflection (you can’t iterate over const enum values)
- Debugging (values are replaced at compile time)
Consider using string literal unions for better tree-shaking and runtime safety.
Naming Conventions
// ✅ Good: PascalCase for enum name, UPPER_CASE for string values
enum HttpStatus {
OK = "OK",
NOT_FOUND = "NOT_FOUND",
INTERNAL_ERROR = "INTERNAL_SERVER_ERROR"
}
// ✅ Good: PascalCase for enum name and values (numeric enums)
enum Direction {
Up,
Down,
Left,
Right
}
// ❌ Avoid: Inconsistent naming
enum badExample {
first = "FIRST",
Second = "second",
THIRD = "Third"
}