Enums in TypeScript provide a way to define a set of named constants. They allow you to create a collection of related values that can be used as a type. Enums make your code more readable, maintainable, and less prone to errors by giving meaningful names to sets of numeric or string values.
TypeScript also supports string enums, where each member has a string value:
enumDirection { North ="NORTH", East ="EAST", South ="SOUTH", West ="WEST",}let myDirection:Direction=Direction.North;console.log(myDirection); // "NORTH"
String enums have better readability and debugging experience compared to numeric enums because the values are meaningful when inspected at runtime. However, they don't support reverse mapping (you can't access Direction["NORTH"]).
Individual enum members can also serve as types. Let's look at an example:
enumShapeKind { Circle, Square, Triangle,}interfaceCircle { kind:ShapeKind.Circle; radius:number;}interfaceSquare { kind:ShapeKind.Square; sideLength:number;}let circle:Circle= { kind:ShapeKind.Circle, radius:10,};// This would cause a type error:// let invalidCircle: Circle = {// kind: ShapeKind.Square, // Error: Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'// radius: 10// };
In this example, we're using specific enum members (ShapeKind.Circle and ShapeKind.Square) as types for the kind property, ensuring type safety.
Enums can be combined with union types to create powerful type constraints:
enumStatus { Active, Inactive, Pending,}// Use a subset of enum values as a typetypeAvailableStatus=Status.Active|Status.Pending;functionprocessUser(userId:string, status:AvailableStatus) {// Process only users with Active or Pending status}// ValidprocessUser("123",Status.Active);processUser("456",Status.Pending);// Invalid - will not compile// processUser("789", Status.Inactive);
Enums exist at runtime as real objects. This is different from TypeScript's other type constructs (like interfaces), which are erased during compilation:
enumDirection { North, East, South, West,}// You can pass the enum as a parameterfunctionprintEnum(enumObject:any) {Object.keys(enumObject).filter((key) =>!isNaN(Number(key))).forEach((key) => {console.log(`${key}: ${enumObject[key]}`); });}printEnum(Direction);// 0: "North"// 1: "East"// 2: "South"// 3: "West"
This runtime presence allows for more dynamic operations with enums, but also increases the size of your generated JavaScript.
enumLogLevel { None =0, Error =1, Warning =2, Info =4, Debug =8, All = Error | Warning | Info | Debug,}classLogger {private level:LogLevel;constructor(level:LogLevel) {this.level = level; }log(message:string, messageLevel:LogLevel):void {// Use bitwise AND to check if this level is enabledif (this.level & messageLevel) {constprefix= LogLevel[messageLevel] ||"LOG";console.log(`[${prefix}] ${message}`); } }}// Create a logger that only shows errors and warningsconstlogger=newLogger(LogLevel.Error |LogLevel.Warning);logger.log("System starting up",LogLevel.Info); // Not loggedlogger.log("Missing optional config",LogLevel.Warning); // Loggedlogger.log("Failed to connect",LogLevel.Error); // Loggedlogger.log("Data structure details",LogLevel.Debug); // Not logged
This example uses bit flags to create a combination of log levels.
Example 3: State Machine for Order Processing
enumOrderState { Created, Processing, Shipped, Delivered, Canceled,}classOrder {private state:OrderState;constructor(public id:string,public customerName:string ) {this.state =OrderState.Created; }getState():OrderState {returnthis.state; }getStateAsString():string {return OrderState[this.state]; }processOrder():boolean {if (this.state ===OrderState.Created) {this.state =OrderState.Processing;console.log(`Order ${this.id} is now being processed`);returntrue; }console.log(`Cannot process order ${this.id} in ${this.getStateAsString()} state`);returnfalse; }shipOrder():boolean {if (this.state ===OrderState.Processing) {this.state =OrderState.Shipped;console.log(`Order ${this.id} has been shipped`);returntrue; }console.log(`Cannot ship order ${this.id} in ${this.getStateAsString()} state`);returnfalse; }deliverOrder():boolean {if (this.state ===OrderState.Shipped) {this.state =OrderState.Delivered;console.log(`Order ${this.id} has been delivered`);returntrue; }console.log(`Cannot deliver order ${this.id} in ${this.getStateAsString()} state`);returnfalse; }cancelOrder():boolean {if (this.state !==OrderState.Delivered &&this.state !==OrderState.Canceled) {this.state =OrderState.Canceled;console.log(`Order ${this.id} has been canceled`);returntrue; }console.log(`Cannot cancel order ${this.id} in ${this.getStateAsString()} state`);returnfalse; }}// Usageconstorder=newOrder("ORD123","John Doe");console.log(`New order state: ${order.getStateAsString()}`); // "Created"order.processOrder(); // "Order ORD123 is now being processed"order.shipOrder(); // "Order ORD123 has been shipped"order.deliverOrder(); // "Order ORD123 has been delivered"// Try to cancel a delivered orderorder.cancelOrder(); // "Cannot cancel order ORD123 in Delivered state"
This example demonstrates using enums to track the state of an order through its lifecycle.
// GoodenumHttpStatus { OK =200, NotFound =404,}// Not recommendedenumhttpStatus { ok =200, notFound =404,}
2. Use string enums for better readability
// Better - values are meaningful when debuggingenumDirection { North ="NORTH", East ="EAST", South ="SOUTH", West ="WEST",}// Less clear at runtimeenumDirection { North,// 0 East,// 1 South,// 2 West,// 3}
3. Use const enums for better performance when possible
4. Avoid using enum as a parameter type; use union of specific values instead
enumStatus { Active, Inactive, Pending,}// Less desirable: allows any Status valuefunctionprocessStatus(status:Status) {/* ... */}// Better: restricts to specific valuesfunctionprocessStatus(status:Status.Active|Status.Pending) {/* ... */}
5. Be careful with computed values in enums
Computed values can lead to unexpected behavior and are evaluated at runtime rather than compile time. Use them only when necessary.
6. For bit flags, use powers of 2 and document the purpose
7. Consider alternatives to enums when appropriate
TypeScript offers other ways to represent a fixed set of values:
// Union of string literalstypeDirection="North"|"East"|"South"|"West";// For constant values that need a namespaceconstHttpStatus= { OK:200, Created:201, BadRequest:400, NotFound:404,} asconst;typeHttpStatus= (typeof HttpStatus)[keyoftypeof HttpStatus];
These alternatives can sometimes offer better type safety or smaller compiled code.
Create a DaysOfWeek enum with values for each day of the week. Then write a function that takes a day value and returns whether it's a weekday or weekend.
Exercise 2: String Enum with API Status Codes
Create a string enum to represent different API response statuses. Write a function that simulates an API call and returns a random status.
Exercise 3: Enum with Bit Flags
Create an enum to represent different user permissions using bit flags. Then write functions to check if a user has specific permissions and to add or remove permissions.