TypeScript enums provide a powerful way to define named constants, but iterating over them requires understanding their unique behavior. Unlike arrays, enums compile to different JavaScript structures depending on their type, which affects how you can iterate through their keys and values.
This comprehensive guide covers every iteration method, from basic approaches to advanced patterns, with practical examples for modern web development projects built with Next.js and React.
What you'll learn:
- The three types of TypeScript enums and their runtime behavior
- Multiple iteration methods including Object.keys(), Object.values(), and for...in loops
- Type-safe patterns for reliable enum iteration
- Performance considerations and best practices
- Common pitfalls and how to avoid them
Understanding enum iteration is essential when building type-safe applications that rely on constant values for configuration, status tracking, or configuration management.
Understanding TypeScript Enum Types
Before diving into iteration techniques, it's essential to understand the three types of TypeScript enums and how they behave at runtime. Each type compiles differently, which directly impacts your iteration strategy.
Numeric Enums
Numeric enums are the most common type and auto-increment by default. When you declare an enum without explicit values, TypeScript assigns sequential numbers starting from 0.
enum Direction {
North, // 0
East, // 1
South, // 2
West // 3
}
Numeric enums produce both a forward mapping (name to value) and a reverse mapping (value to name) at runtime. This dual-mapping behavior creates interesting iteration possibilities but also introduces overhead.
String Enums
String enums provide more semantic value but behave differently at runtime. They don't have reverse mappings, which affects iteration performance and approach.
enum Status {
Pending = "PENDING",
Active = "ACTIVE",
Completed = "COMPLETED"
}
Const Enums
Const enums are completely inlined at compile time, meaning they don't exist as objects at runtime. This has significant implications for iteration--you cannot iterate over const enums using standard object methods because they disappear after compilation.
const enum Priority {
Low = "LOW",
Medium = "MEDIUM",
High = "HIGH"
}
Key insight: Only regular (non-const) enums can be iterated at runtime. Use const enums when you need performance for direct access but avoid them when iteration is required.
When to Use Each Enum Type
| Type | Use When | Iteration Support | Performance |
|---|---|---|---|
| Numeric | Related numeric values, flags | Full support | Moderate |
| String | Meaningful string values, API responses | Full support | Good |
| Const | Performance-critical, no iteration needed | None | Excellent |
Methods for Iterating Over Enums
TypeScript provides several approaches for iterating over enums, each with distinct characteristics suited to different scenarios.
Using Object.keys() for Key Iteration
The Object.keys() method returns an array of the enum's keys, which correspond to the enum member names. This approach works reliably for both numeric and string enums.
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
const colorKeys = Object.keys(Color);
// ["Red", "Green", "Blue"]
For numeric enums, Object.keys() returns only the member names, not the numeric values. You'll need additional processing to access both keys and values.
Using Object.values() for Value Iteration
Object.values() returns an array of the enum's values, providing direct access to the underlying data.
enum HTTPStatus {
OK = 200,
Created = 201,
Accepted = 202
}
const statusValues = Object.values(HTTPStatus);
// [200, 201, 202, "OK", "Created", "Accepted"]
Important: For numeric enums, Object.values() returns both the numeric values AND the string names due to the reverse mapping. This dual return requires filtering if you want only one type of value.
Using for...in Loops
The for...in statement iterates over all enumerable string properties of an object, making it suitable for enum iteration with more control over the process.
enum Transport {
Car = "CAR",
Train = "TRAIN",
Plane = "PLANE"
}
for (const key in Transport) {
console.log(`${key}: ${Transport[key]}`);
}
// Car: CAR
// Train: TRAIN
// Plane: PLANE
for...in provides access to both keys and values during iteration, which can be useful when you need to perform operations using both.
Using for...of with Object.entries()
Combining for...of with Object.entries() provides a clean, readable approach to enum iteration with full access to both keys and values.
enum Season {
Spring = "SPRING",
Summer = "SUMMER",
Autumn = "AUTUMN",
Winter = "WINTER"
}
for (const [key, value] of Object.entries(Season)) {
console.log(`${key} => ${value}`);
}
// Spring => SPRING
// Summer => SUMMER
// Autumn => AUTUMN
// Winter => WINTER
Choose the right approach for your use case
Object.keys()
Returns enum member names as an array. Simple and predictable for all enum types.
Object.values()
Returns all values including reverse mappings. Requires filtering for numeric enums.
for...in
Iterates with direct access to keys and values. Good for complex iteration logic.
Object.entries() + for...of
Clean syntax with destructuring. Most readable for modern TypeScript codebases.
Type-Safe Enum Iteration
TypeScript's type system can help ensure safe enum iteration, preventing runtime errors and improving code reliability. When building scalable web applications, proper type safety becomes essential for maintainability.
Using Type Guards
Type guards help filter out unwanted entries when iterating, particularly useful for numeric enums where reverse mappings appear alongside regular members.
enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3
}
function isLogLevelKey(key: string): key is keyof typeof LogLevel {
return key in LogLevel;
}
Object.keys(LogLevel).filter(isLogLevelKey);
// ["Debug", "Info", "Warn", "Error"]
Excluding Reverse Mappings
Numeric enums create reverse mappings that appear as additional properties. Several techniques help exclude these from iteration results.
enum Priority {
Low = 1,
Medium = 2,
High = 3
}
// Filter approach: exclude numeric keys
const enumKeys = Object.keys(Priority).filter(
key => isNaN(Number(key))
);
// ["Low", "Medium", "High"]
// Alternative: use type assertions
const directKeys = Object.keys(Priority) as (keyof typeof Priority)[];
Reusable Utility Functions
Create reusable utilities for type-safe enum iteration across your codebase. These patterns are especially valuable in large-scale TypeScript projects where consistency matters.
function getEnumKeys<T extends object>(enumObj: T): (keyof T)[] {
return Object.keys(enumObj).filter(
key => isNaN(Number(key))
) as (keyof T)[];
}
function getEnumValues<T extends object>(enumObj: T): T[keyof T][] {
return Object.values(enumObj).filter(
value => typeof value === "string" || typeof value === "number"
) as T[keyof T][];
}
For more TypeScript patterns and techniques, explore our comprehensive guides on advanced type manipulation.
Performance Considerations
Understanding performance characteristics helps choose the right iteration method for your specific use case.
Runtime Object Creation
Object.keys() and Object.values() create new arrays on each call, which can impact performance in tight loops or large enums. For frequently iterated enums, consider caching the results.
// Cache iteration results for frequently-used enums
const cachedColorKeys = Object.keys(Color);
const cachedColorValues = Object.values(Color);
for...in vs. Object Methods
for...in loops are generally more memory-efficient for large enums since they don't create intermediate arrays, though the difference is negligible for typical use cases with fewer than 100 enum members.
Const Enum Trade-offs
While const enums eliminate runtime overhead entirely, this benefit comes at the cost of iteration capability. Use const enums when you need performance for direct access but avoid them when iteration is required.
// Good: const enum for direct access
const enum Config {
MaxRetries = 5
}
const retries = Config.MaxRetries; // Inlined at compile time
// Bad: const enum when iteration is needed
const enum Status {
Pending = "PENDING"
}
// Object.keys(Status) will not work as expected!
Performance Comparison Table
| Method | Time Complexity | Space Complexity | Best For |
|---|---|---|---|
| Object.keys() | O(n) | O(n) | Simple key iteration |
| Object.values() | O(n) | O(n) | Value collection |
| for...in | O(n) | O(1) | Memory-constrained scenarios |
| Object.entries() + for...of | O(n) | O(n) | Key-value pairs |
Where n is the number of enum members
Common Patterns and Best Practices
Pattern: Enum Member Metadata
For complex scenarios, consider storing additional metadata with your enums.
enum APIEndpoint {
Users = "/api/users",
Posts = "/api/posts"
}
const endpointMetadata: Record<keyof typeof APIEndpoint, string> = {
Users: "User management endpoints",
Posts: "Blog post endpoints"
};
Pattern: Dynamic Enum Creation
For scenarios where enum values come from configuration or API responses, consider using TypeScript's mapped types instead of traditional enums.
// Instead of traditional enum when values are dynamic
type Status = {
pending: "pending";
active: "active";
completed: "completed";
};
const Status = {
pending: "pending" as const,
active: "active" as const,
completed: "completed" as const
};
// Still iterable with Object.keys()
Object.keys(Status); // ["pending", "active", "completed"]
Best Practices Summary
- Use type guards when filtering enum members to ensure type safety
- Cache results for frequently iterated enums to improve performance
- Prefer regular enums over const enums when iteration is required
- Document enum behavior especially when using reverse mappings
- Consider mapped types for dynamic or configurable enum-like structures
Related Resources
Explore more TypeScript patterns and best practices in our web development guides covering React patterns, CSS variables, and form structure.
Troubleshooting Common Issues
Why am I getting undefined values during iteration?
When iterating numeric enums, accessing values through string keys can return undefined if the reverse mapping is unexpectedly absent. Use type guards to ensure you're only accessing valid enum members.
Why does Object.values() return both numbers and strings?
Numeric enums create reverse mappings at runtime. Object.values() returns both the numeric values and the string names because both exist as properties. Filter by type if you need only one type of value.
Why can't I iterate over my const enum?
Const enums are completely inlined at compile time and don't exist as JavaScript objects at runtime. Use regular enums when iteration is required, or use object literals with 'as const' instead.
How do I iterate only over enum keys or values?
Use Object.keys() for only names, or filter Object.values() to exclude reverse mappings. For numeric enums, filter out numeric keys: Object.keys(enum).filter(k => isNaN(Number(k))).
Conclusion
Iterating over TypeScript enums requires understanding their runtime behavior and choosing the appropriate method for your use case. Here's a quick reference:
For most applications: Object.keys() combined with type guards provides the best balance of type safety and simplicity.
For memory efficiency: Use for...in loops when iterating large enums in performance-critical code.
For readability: Combine Object.entries() with for...of for clean, modern TypeScript code.
Remember: Reserve const enums for performance-critical direct access scenarios where iteration isn't needed.
By following the patterns and best practices outlined in this guide, you can write robust, maintainable code that effectively handles enum iteration in your TypeScript projects.
Sources
- LogRocket: How to iterate over enums in TypeScript - Comprehensive coverage of iteration methods
- TypeScript Handbook: Enums - Official documentation on enum behavior