As a developer who has worked with TypeScript for years, I’ve found enums very useful. You should know how to work with TypeScript enums, so I wrote a complete tutorial with practical examples about Enums in TypeScript. By the end of this tutorial, you will become super confident about Enums.
What are TypeScript Enums?
TypeScript enums allow developers to define a set of named constants, making it easier to document intent and create distinct cases in your code. Think of them as a way to define a collection of related values that can be used throughout your TypeScript application.
Think of enums in TypeScript as custom data types with a fixed set of allowed values. For example, instead of remembering that status code 1 means “pending” and 2 means “completed,” you can create an enum with descriptive names.
Let’s start with a simple example of a TypeScript Enum.
enum Direction {
Up,
Down,
Left,
Right
}
// Usage
const myDirection: Direction = Direction.Up;
Create Enums in TypeScript
In TypeScript, enums are created using the enum keyword. The basic structure includes the keyword, a name, and a list of members within curly braces.
enum Direction {
North,
East,
South,
West
}
By default, enum members are assigned numeric values starting at 0. You can also explicitly set values:
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
TypeScript supports both numeric and string enums. String enums require each member to have an explicit string value:
enum FileType {
PDF = "application/pdf",
DOCX = "application/vnd.openxmlformats"
}
Check out How to Convert String to Enum in Typescript?
Why Use TypeScript Enums?
Before diving deeper, let’s understand why you might want to use enums in your projects:
- Improved code readability – Enums significantly improve code readability by replacing magic numbers or strings with descriptive names.
- Type safety – TypeScript’s compile-time checking with enums helps catch errors early. The compiler will flag any attempt to use values not defined in the enum, preventing potential bugs. TypeScript ensures you only use valid enum values
- Self-documentation – Enums create a form of documentation within your code, helping new team members understand the valid values for a particular concept.
- Intellisense support – Enums also provide better code navigation and autocompletion in modern IDEs. When you type the enum name followed by a dot, you’ll see a list of all available options. You will easily get autocomplete features in your IDE for Enums.
Types of Enums in TypeScript
You can create and use different types of Enums in TypeScript applications. I have explained with examples.
Numeric Enums
Numeric enums in TypeScript store number values by default. When you create a numeric enum without assigning values, TypeScript automatically assigns incremental numbers starting from 0.
By default, TypeScript enums are numeric, starting at 0 and incrementing by 1:
enum UserRole {
Admin, // 0
Editor, // 1
Author, // 2
Subscriber // 3
}
Developers can also assign custom numeric values. If only some members have values, TypeScript will calculate the rest by incrementing from the last defined value.
enum StatusCode {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404
}
Numeric enums create a reverse mapping in JavaScript, allowing lookups by value and name. This feature makes them useful when working with numeric constants that have meaningful names.
Check out TypeScript Switch with Enums
String Enums
TypeScript String enums require explicit string values for each member. They don’t support auto-incrementing like numeric enums.
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
String enums are more descriptive when debugging since the values appear as strings in the compiled code. They don’t create reverse mappings in JavaScript.
These enums represent fixed sets of string constants, like API routes or event types. They provide better type safety than simple string literals.
enum ApiEndpoint {
Users = "/api/users",
Products = "/api/products",
Orders = "/api/orders"
}
Heterogeneous Enums
Heterogeneous enums in TypeScript combine both string and numeric values within a single enum. While possible, TypeScript experts often recommend avoiding this pattern.
enum Mixed {
Id = 1,
Name = "NAME",
Active = true // TypeScript 4.1+ allows this
}
These mixed enums can lead to confusion and potential bugs. They lose some of the benefits of consistent typing that regular enums provide.
Most developers prefer using consistent value types within an enum to maintain code clarity. Consider using separate enums or other TypeScript constructs when mixed types are needed.
Const Enums
For performance optimization, TypeScript offers const enums that are entirely removed during compilation:
const enum DeviceType {
Mobile = "mobile",
Tablet = "tablet",
Desktop = "desktop"
}
// Usage
const myDevice = DeviceType.Mobile;
This will be compiled to:
const myDevice = "mobile";
Check out TypeScript Enum Reverse Mapping
TypeScript Enum Members
Enum members in TypeScript represent individual values within an enum type. Depending on how they’re defined, these members store constants and can hold string values, numeric values, or a mix of both.
Constant Members
Constant members are enum values that TypeScript can fully evaluate at compile time. These members receive their values in three ways:
- Implicit initialization: When no value is provided, TypeScript assigns incrementing numbers starting from 0.
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
- Explicit initialization: Values are directly assigned using literals.
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
Constant expressions: Values calculated from other constant values.
enum FileAccess {
Read = 1,
Write = 2,
ReadWrite = Read | Write // 3
}
Constant members make code more readable by replacing magic numbers with named constants, improving maintainability and reducing errors.
Computed Members
Computed members receive values from expressions that TypeScript can’t evaluate until runtime. These members use expressions that aren’t constant expressions.
enum FileSize {
Small = getSize("small"),
Medium = calculateValue(),
Large = Small * 5
}
function getSize(type: string) { return type === "small" ? 10 : 20; }
function calculateValue() { return Math.random() * 100; }
Essential rules for computed members:
- Any enum member after a computed member must have an explicit value
- Can reference previously defined constants
- Can use function calls or complex expressions
- Runtime evaluation means values aren’t known until execution
Computed members are helpful when enum values need to be determined dynamically or depend on external factors.
Ambient Enums
Ambient enums are declared using the declare keyword and primarily used in declaration files (.d.ts) to describe existing enum shapes from external JavaScript code.
declare enum API {
Endpoint,
Version,
Key
}
Key characteristics of ambient enums:
- They don’t generate implementation code
- Members without initializers are always considered computed
- Used to describe enums defined elsewhere (like in JavaScript libraries)
- Useful in type definitions for JavaScript code
Ambient enums serve as a bridge between TypeScript’s type system and JavaScript code that conceptually uses enum-like objects. They allow TypeScript to type-check code that uses external JavaScript constants.
Check out TypeScript Enum vs Union Type
TypeScript Enums as Function Parameters
Now, let me show you how to use enums as a function parameter in TypeScript.
Enums make function parameters more readable and type-safe. They restrict inputs to a predefined set of values, reducing bugs from invalid arguments.
enum UserRole {
Admin = "ADMIN",
Editor = "EDITOR",
Viewer = "VIEWER"
}
function setUserPermissions(role: UserRole) {
// Type-safe logic based on role
if (role === UserRole.Admin) {
// Admin-specific code
}
}
// Valid call
setUserPermissions(UserRole.Editor);
// Error: Argument of type '"MANAGER"' is not assignable to parameter of type 'UserRole'
setUserPermissions("MANAGER");
This prevents passing arbitrary strings and catches errors at compile time instead of runtime.
Read Get Key by Value from enum String in Typescript
Enums in TypeScript Switch Statements
Now, let me show you how to use enums in Switch statements in TypeScript. TypeScript can even warn about missing enum cases.
enum ApiStatus {
Success = "SUCCESS",
Error = "ERROR",
Loading = "LOADING"
}
function handleApiResponse(status: ApiStatus) {
switch (status) {
case ApiStatus.Success:
return processData();
case ApiStatus.Error:
return showError();
case ApiStatus.Loading:
return showSpinner();
default:
// TypeScript can check if all cases are handled
const exhaustiveCheck: never = status;
throw new Error(`Unhandled status: ${exhaustiveCheck}`);
}
}
The never type helps enforce exhaustive checking—if a new enum value is added, the compiler flags the switch as incomplete.
Check out TypeScript String to Number Conversion
TypeScript Enums Real Examples
Now, let me show you some real examples of using TypeScript Enums. I will show you two examples.
Subscription Models
One practical example is using TypeScript enums for subscription tiers in a SaaS application. This is particularly useful for tiers-based subscription models where different account types have varying capabilities:
enum SubscriptionTier {
Free = "FREE",
Basic = "BASIC",
Pro = "PRO",
Enterprise = "ENTERPRISE"
}
interface Subscription {
tier: SubscriptionTier;
features: string[];
price: number;
}
const pricingPlans: Record<SubscriptionTier, Subscription> = {
[SubscriptionTier.Free]: {
tier: SubscriptionTier.Free,
features: ["Basic analytics", "1 project"],
price: 0
},
[SubscriptionTier.Basic]: {
tier: SubscriptionTier.Basic,
features: ["Advanced analytics", "5 projects", "Email support"],
price: 9.99
},
[SubscriptionTier.Pro]: {
tier: SubscriptionTier.Pro,
features: ["Team collaboration", "Unlimited projects", "Priority support"],
price: 29.99
},
[SubscriptionTier.Enterprise]: {
tier: SubscriptionTier.Enterprise,
features: ["Custom integrations", "Dedicated account manager", "SLA"],
price: 99.99
}
};
UI State Management
Enums are excellent for managing UI states in React applications. Here is an example.
enum LoadingState {
Idle = "IDLE",
Loading = "LOADING",
Success = "SUCCESS",
Error = "ERROR"
}
function DataFetcher() {
const [state, setState] = useState<LoadingState>(LoadingState.Idle);
useEffect(() => {
setState(LoadingState.Loading);
fetchData()
.then(() => setState(LoadingState.Success))
.catch(() => setState(LoadingState.Error));
}, []);
return (
<div>
{state === LoadingState.Loading && <Spinner />}
{state === LoadingState.Success && <DataView />}
{state === LoadingState.Error && <ErrorMessage />}
</div>
);
}
This pattern is beneficial in ReactJS applications where you need to track different states of a component.
Check out Get Distinct Values from an Array in TypeScript
Best Practices for TypeScript Enums
After working with TypeScript enums across various projects, I figured out some best practices. I will recommend using these best practices.
1. Prefer String Enums for Most Cases
String enums provide a better debugging experience and more meaningful values when logging:
// Good
enum LogLevel {
Info = "INFO",
Warning = "WARNING",
Error = "ERROR",
Debug = "DEBUG"
}
// Instead of numeric values
enum LogLevel {
Info, // 0
Warning, // 1
Error, // 2
Debug // 3
}
2. Be Explicit with Enum Values
Don’t rely on implicit values as they can lead to confusion:
// Bad
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// Good
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
3. Use Const Enums for Performance When Applicable
You can use const enums for performance when applicable in TypeScript.
// More efficient
const enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE"
}
4. Avoid Reverse Lookups
Reverse lookups with numeric enums can be confusing:
enum Color {
Red = 1,
Green = 2,
Blue = 3
}
// This works with numeric enums, but is confusing:
const colorName = Color[2]; // "Green"
Read How to Check if a String is Empty in TypeScript?
Alternatives to TypeScript Enums
While enums are powerful, they’re not always the best choice. Here are some alternatives that might better suit your needs:
Union Types
For simple cases, a union of string literals can be more type-safe:
type Theme = "light" | "dark" | "system";
function setTheme(theme: Theme) {
// Implementation
}
// Usage
setTheme("light"); // Valid
setTheme("custom"); // Type error!
Const Objects
Objects with as const assertion provides similar benefits with better type inference:
const PaymentStatus = {
Pending: "PENDING",
Processing: "PROCESSING",
Completed: "COMPLETED",
Failed: "FAILED",
} as const;
type PaymentStatusType = typeof PaymentStatus[keyof typeof PaymentStatus];
function processPayment(status: PaymentStatusType) {
// Implementation
}
Discriminated Unions
For more complex cases, discriminated unions offer great type safety:
type UserEvent =
| { type: "LOGIN", userId: string, timestamp: number }
| { type: "LOGOUT", userId: string, timestamp: number }
| { type: "PURCHASE", userId: string, productId: string, amount: number };
function handleUserEvent(event: UserEvent) {
switch (event.type) {
case "LOGIN":
// TypeScript knows this has userId and timestamp
console.log(`User ${event.userId} logged in`);
break;
case "LOGOUT":
console.log(`User ${event.userId} logged out`);
break;
case "PURCHASE":
// TypeScript knows this has productId and amount
console.log(`User ${event.userId} purchased ${event.productId}`);
break;
}
}

Conclusion
TypeScript enums are very useful for creating clean, maintainable, and self-documenting code.
As someone who’s worked with TypeScript across numerous projects, I recommend being thoughtful about when to use enums versus alternatives. The best approach depends on your specific use case:
- Use string enums when you need a fixed set of named constants with meaningful string values
- Consider union types for simple cases where you need string literals with excellent type safety
- Use const objects with
as constwhen you need both runtime and type-time enum-like behavior
Do let me know if this tutorial is helpful.
You may also like:
- How to Check if a String Contains a Substring in TypeScript?
- How to Capitalize the First Letter in TypeScript?
- How to Iterate Over Enums in TypeScript?
- TypeScript Enum Naming Conventions
- Check If a String Is in an Enum in TypeScript
- Check If a Value Is in an Enum in TypeScript
- How to Convert Enums to Strings in TypeScript?
- TypeScript Enum Duplicate Values
- How to Convert TypeScript Enum to Number
- How to Sort by Enum in TypeScript?
- TypeScript Enum vs Const
- How to Compare String to Enum in TypeScript

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.