---
title: A Detailed Guide on TypeScript Enum with Examples
description: We'll explore TypeScript Enums with examples.
slug: typescript-enum
authors: abdullah_numan
tags: [typescript]
image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-05-21-typescript-enums/social-2.png
hide_table_of_contents: false
---

**This article was last updated on January 6, 2025, to include sections on Common Mistakes with TypeScript Enums and Tips for Using Enums in TypeScript Classes.**

## Introduction

### TL;DR;

:::info FAQ

1. What is an enum in TypeScript?

- Enumeration or enum is a feature in TypeScript, which allows a set of named constants. This makes the code more readable and maintainable.

2. What kinds of enums are there in TypeScript?

- TypeScript supports two kinds of enums: string enums, such as enum Color { RED = "Red" }; and numeric enums, such as enum Day { MONDAY = 0 };.

3. How do I initialize enum members in TypeScript?

- String enums must be initialized explicitly; numeric enums can be uninitialized and will auto-increment starting from 0.

4. What are the differences between constant and computed values in enums?

- Constant means values are fixed, such as "Red", 0; computed means values are derived from expressions, such as "A".length.

5. How do enums behave at runtime?

- Enums create the JavaScript object versions at runtime; this therefore includes the ability for bi-directional mapping when these are numeric enums but still only unidirectional mapping exists for string enums
  :::

**Enum**s are constants based data structures that store a set of named constants grouped around a central theme or intent. In TypeScript, Enums are a feature that injects runtime JavaScript objects to an application in addition to providing usual type-level extensions.

This post explores **enums** in TypeScript with examples from a tiers based Subscription model where subscription entities differ according to account types and billing schedules.

While examining the examples, we discuss underlying **enums** concepts including **`enum` member** types such as **string** or **numeric** and **constant** or **computed**; **homogeneity** and **heterogeneity** of `enum`s as well as **member initialization** with or without setting a value. We also explore how an enum translates to an IIFE during compilation, carries out **directional mapping** and injects its JavaScript object to the runtime environment. We examine and leverage the individual types generated by enum members to define our own subtypes and see how the main enum type generates a union of member keys. Lastly, we bring all these enum concepts together to implement a simple `PersonalSubscription` class.

In the sections ahead, we relate to examples for the tiers based Subscription model and analyze them to discuss underlying concepts and behaviors.

Steps well'll cover:

- [TypeScript Enums Examples](#typescript-enums-examples)
- [`enum` Types in TypeScript](#enum-types-in-typescript)
- [Enum Member Initialization in TypeScript](#enum-member-initialization-in-typescript)
- [TypeScript Enums at Compile Time and Runtime](#typescript-enums-at-compile-time-and-runtime)
- [Enum Member Values in TypeScript: Constant vs Computed](#enum-member-values-in-typescript-constant-vs-computed)
- [Types from TypeScript Enums](#types-from-typescript-enums)
- [Using TypeScript Enums in Classes](#using-typescript-enums-in-classes)
- [Practical Applications of TypeScript Enums](#practical-applications-of-typescript-enums)
- [Common Pitfalls Using Enums](#common-pitfalls-using-enums)

## Prerequisites

In order to properly follow this post and test out the examples, you need to have a JavaScript engine. It could be **Node.js** in your local machine with TypeScript supported or you could use the [TypeScript Playground](https://www.typescriptlang.org/play#code).

## TypeScript Enums Examples

In order to illustrate `enum`s concepts in TypeScript, we are using a tiers based Subscription model. Let's say, we have a subscription entity stored in a `subscriptions` table. And it has `accountType` and `billingSchedule` attributes.

`accountType`s can be one of `Personal`, `Startup`, `Enterprise` or `Custom`. `billingSchedule` can be categorized as one of `Free`, `Monthly`, `Quarterly` or `Yearly`. These possible options indicate an intent to group a subscription based on account type and billing schedule. We can use TypeScript `enum`s to define types for these attributes. Using `enum`s not only allows us to declare types for `accountType` and `billingSchedule`, but also creates representative runtime objects which would otherwise need to be produced from a database table for reference.

So, to start the proceedings let's define a couple of enums. We can declare the `enum` for `accountType` attribute like this:

```tsx
enum AccountType {
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
  CUSTOM = "Custom",
}
```

We are using `string` literals to initialize all `enum` members in `AccountType`. This is an example of **string enum**s. Further elaboration ahead in [an upcoming section](#string-enums-in-typescript).

One way of defining an `enum` for `billingSchedule` looks like this:

```tsx
enum BillingSchedule {
  FREE = 0,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}
```

Here, we are using a numeric literal to initialize the first member of `BillingSchedule`. This is an example of **numeric enum**s. More on this in [a later section](#numerical-enums-in-typescript).

Let's quickly test out the runtime role of enums as we start discussing underlying `enum`s concepts.

### `enum`s Produce Runtime JavaScript Objects

We mentioned before that `enum`s inject JS objects to runtime environment. This can be observed when we run the following snippet:

```tsx
const accountType = AccountType.PERSONAL;
const billingSchedule = BillingSchedule.FREE;

console.log(accountType); // "Personal"
console.log(billingSchedule); // 0
```

With `AccountType.PERSONAL` and `BillingSchedule.FREE`, we are calling actual objects at runtime and getting appropriate responses. These indicate that TypeScript `enum` definitions are not just simple type definitions, but also introduce JS objects to our application. We revisit this in [a later part of this post](#typescript-enums-at-compile-time-and-runtime).

## `enum` Types in TypeScript

Enum members are typically used to store constants. Members can have string constants, numerical constants or mix of both. Homogeneity of member values determines whether the enum is a **string enum** or a **numerical enum**.

### String Enums in TypeScript

When all members of an enum have string values, it is a string enum. As in `AccountType`:

```tsx
enum AccountType {
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
  CUSTOM = "Custom",
}
```

### Numerical Enums in TypeScript

Similarly, when all members have numerical values, the enum itself becomes numerical:

```tsx
enum BillingSchedule {
  FREE = 0,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}
```

Here, `BillingSchedule` has the first member initialized to a number, and the subsequent ones are uninitialized but TypeScript's enum defaults auto-increment them by `1`. So, all members here are numerical and ` BillingSchedule` is a numerical enum. We discuss this more in the next section on enum member initialization.

## Enum Member Initialization in TypeScript

String enum members must be initialized explicitly with string values. In numerical enums, they may remain uninitialized and the value is then assigned implicitly by TypeScript.

### Member Initialization in TypeScript String Enums

For string enums, as we can see in our `AccountType` example, we are explicitly initializing all members:

```tsx
enum AccountType {
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
  CUSTOM = "Custom",
}
```

Here, we are using string literals to meaningfully document and describe our intent of grouping account types in to several options, which is useful for our application features and developer experience. Explicit string initialization also helps with serialization of the JS object created at runtime.

An uninitialized member coming after a string member is invalid:

```tsx
enum AccountType {
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
  CUSTOM,
}

/*
Enum member must have initializer.(1061)
(enum member) AccountType.CUSTOM
*/
```

If `CUSTOM` is uninitialized as the first member, it is assigned `0` by default, but we then have a **heterogenous** enum with a numeric member mixed with string members:

```tsx
// Heterogenous enum

enum AccountType {
  CUSTOM,
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
}

const accountTypeCustom = AccountType.CUSTOM;
console.log(accountTypeCustom); // 0
```

### Member Initialization in TypeScript Numerical Enums

Members in a numerical enum may or may not be initialized explicitly. Uninitialized members are assigned implicit default values.

**Explicitly Initialized Numerical Members**

In our `BillingSchedule` enum, we explicitly assigned `0` to the first member:

```tsx
// First member initialized, subsequent members auto-increment

enum BillingSchedule {
  FREE = 0,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}
```

The subsequent members get an auto-incremented value increased by `1`:

```tsx
console.log(BillingSchedule.MONTHLY); // 1
console.log(BillingSchedule.QUARTERLY); // 2
console.log(BillingSchedule.YEARLY); // 3
```

As we can see, initializing a member with a number represents an offset value based on which auto-incremented values of subsequent members are determined. Assigning the first member with `0` represents a zero offset. We could have been better off without initialization of a member at all, like this:

```tsx
// No initialization

enum BillingSchedule {
  FREE,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}

console.log(BillingSchedule.FREE); // 0
```

This is because the default offset for first member is `0`. This definition offers more convenience.

We can assign an offset anywhere and it would reflect in subsequent implicitly incremented member values:

```tsx
enum BillingSchedule {
  FREE,
  MONTHLY,
  QUARTERLY = 5,
  YEARLY,
}

console.log(BillingSchedule.MONTHLY); // 1
console.log(BillingSchedule.QUARTERLY); // 5
console.log(BillingSchedule.YEARLY); // 6
```

## TypeScript Enums at Compile Time and Runtime

At compile time, TypeScript translates an enum to a corresponding IIFE which then introduces into runtime a JavaScript object representation of the enum.

String members and numeric members behave differently at compilation. A string member gets mapped **unidirectionally** to its corresponding JavaScript object property. In contrast, a numeric member gets mapped **bi-directionally** to its runtime object property. So, as we see in the sections below, string enums are limited to unidirectional navigation, but numeric members offer us the convenience of bidirectional access to constants.

### TypeScript String Enums Have Unidirectional Mapping

The `AccountType` enum we declared in the beginning compiles to the following JS code:

```tsx
/*
enum AccountType {
    PERSONAL = "Personal",
    STARTUP = "Startup",
    ENTERPRISE = "Enterprise",
    CUSTOM = "Custom"
}
*/

"use strict";
var AccountType;
(function (AccountType) {
  AccountType.PERSONAL = "Personal";
  AccountType["STARTUP"] = "Startup";
  AccountType["ENTERPRISE"] = "Enterprise";
  AccountType["CUSTOM"] = "Custom";
})(AccountType || (AccountType = {}));
```

This IIFE propels the following object to runtime:

```tsx
{
    PERSONAL: "Personal",
    STARTUPP: "Startup",
    ENTERPRISEP: "Enterprise",
    CUSTOMP: "Custom"
}
```

Unidirectional mapping of a string member sets only the constant names as keys and therefore limits access to the enum only via constant names only, not by the value:

```tsx
console.log(AccountType.PERSONAL); // "Personal"
console.log(AccountType.Personal); // Property 'Personal' does not exist on type 'typeof AccountType'. Did you mean 'PERSONAL'?(2551)
```

Accessing the enum via member values is possible in numeric enums, as we'll see next.

### TypeScript Numerical Enums Have Bidirectional Mapping

In contrast to unidirectional mapping of string enums, numerical enums compile to bidirectional JS objects. Our `BillingSchedule` object translates to the following IIFE:

```tsx
/*
enum BillingSchedule {
    FREE,
    MONTHLY,
    QUARTERLY = 5,
    YEARLY
}
*/

"use strict";
var BillingSchedule;
(function (BillingSchedule) {
  BillingSchedule[(BillingSchedule["FREE"] = 0)] = "FREE";
  BillingSchedule[(BillingSchedule["MONTHLY"] = 0)] = "MONTHLY";
  BillingSchedule[(BillingSchedule["QUARTERLY"] = 0)] = "QUARTERLY";
  BillingSchedule[(BillingSchedule["YEARLY"] = 0)] = "YEARLY";
})(BillingSchedule || (BillingSchedule = {}));
```

And it introduces this object to the runtime environment:

```tsx
{
    "0": "FREE",
    "1": "MONTHLY",
    "2": "QUARTERLY",
    "3": "YEARLY",
    "FREE": 0,
    "MONTHLY": 1,
    "QUARTERLY": 2,
    "YEARLY": 3
}
```

So, now we are able to navigate both ways for numeric members:

```tsx
console.log(BillingSchedule.FREE); // 0
console.log(BillingSchedule[0]); // "FREE"
console.log(BillingSchedule.YEARLY); // 3
console.log(BillingSchedule[3]); // "YEARLY"
```

## Enum Member Values in TypeScript: Constant vs Computed

Enum member values can be **constant** or **computed**.

### Constant Values of Enum Members

In both our examples, the value of enums are constant. However, there are subtle differences among constant values too. For example, in the `AccountType` enum, all values are string literals which are considered as **literal enum expressions**:

```tsx
enum AccountType {
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
  CUSTOM = "Custom",
}
```

Similarly, in the following `BillingSchedule` enum, the numeric literal `0` is also a literal enum expression:

```tsx
enum BillingSchedule {
  FREE = 0,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}
```

Uninitialized members which implicitly get assigned numeric literals are also considered constants. As all the members have in this version of `BillingSchedule`:

```tsx
enum BillingSchedule {
  FREE,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}
```

There are other nuanced forms of literal enum expressions, such as a value referenced from another enum member. For the rest of the possible cases, please look up the [TypeScript **Enums** docs](https://www.typescriptlang.org/docs/handbook/enums.html).

### Computed Values of Enum Members

A computed value is assumed when a member's value is computed from a JavaScript expression. We have no such use case in our examples, but a basic instance would look like this:

```tsx
enum ABasicExample {
    A_BASIC_EXAMPLE = "A Basic Example".length;
}
```

## Types from TypeScript Enums

So far, we have explored only the object aspects of TypeScript enums. Let's now consider types act out in enums.

When all members of an enum are literal enum expressions, types for each member are generated from their member names. And the enum itself effectively becomes a union of all the subtypes.

### Individual Types

Individual types are generated from each member when all members of the enum are either string literals or numeric literals. This becomes clear when such standalone types are used to define new subtypes. For example, from our `AccountType` enum, we can produce a few account subtypes which uses the member types:

```tsx
type TPersonalAccount = {
  tier: AccountType.PERSONAL;
  postsQuota: number;
  verified: boolean;
};

interface IStartupAccount {
  tier: AccountType.STARTUP;
  postsQuota: number;
  verified: boolean;
}
```

In the above type definitions, we are using `AccountType.PERSONAL` and `AccountType.STARTUP` enum member types to define new subtypes of accounts.

In a similar vein, let's look at a subtype derived from a `BillingSchedule` member:

```tsx
interface IFreeBilling {
  tier: BillingSchedule.FREE;
  startDate: string | boolean;
  expiryDate: string | boolean;
}
```

### Union of Member Keys

The type generated by the enum itself is effectively a union of all enum member types. It can be accessed with the `keyof typeof` functions chained like this:

```tsx
/*
    enum AccountType {
        PERSONAL = "Personal",
        STARTUP = "Startup",
        ENTERPRISE = "Enterprise",
        CUSTOM = "Custom"
    }
*/

type TAccountType = keyof typeof AccountType;

/*
The generated type is equivalent to:

    type TAccountType = "PERSONAL" | "STARTUP" | ENTERPRISE | "ENTERPRISE" | "CUSTOM";
*/
```

The code above first accesses the enum object with `typeof` and then grabs the member names (keys) with `keyof`.

With these essential concepts covered, let's now see how to use enums and its generated types inside TypeScript classes.

## Using TypeScript Enums in Classes

We can now refactor some of the enums and type definitions and implement a rudimentary `PersonalSubscription` class which uses them:

```tsx
enum AccountType {
  PERSONAL = "Personal",
  STARTUP = "Startup",
  ENTERPRISE = "Enterprise",
  CUSTOM = "Custom",
}

enum BillingSchedule {
  FREE,
  MONTHLY,
  QUARTERLY,
  YEARLY,
}

type TAccount<AccountType> = {
  tier: AccountType;
  postsQuota: number;
  verified: boolean;
};

interface IBilling<BillingSchedule> {
  tier: BillingSchedule;
  startDate: string | boolean;
  expiryDate: string | boolean;
}

class PersonalAccount implements TAccount<AccountType.PERSONAL> {
  tier: AccountType.PERSONAL = AccountType.PERSONAL;
  postsQuota = 2;
  verified = false;
}

class FreeBilling implements IBilling<BillingSchedule.FREE> {
  tier: BillingSchedule.FREE = BillingSchedule.FREE;
  startDate = false;
  expiryDate = false;
}

interface IPersonalSubscription<TAccount, IBilling> {
  accountType: TAccount;
  billingSchedule: IBilling;
  creditCard: string;
}

class PersonalSubscription
  implements
    IPersonalSubscription<
      TAccount<AccountType.PERSONAL>,
      IBilling<BillingSchedule.FREE>
    >
{
  accountType = new PersonalAccount();
  billingSchedule = new FreeBilling();
  creditCard: string = "XXXXXXXXXXXXXXXX";
}
```

In the above code, for `BillingSchedule` we have used a numeric enum with all uninitialized members. The first member is therefore assigned `0` and subsequent ones get auto-incremented by `1`. We have used generics to pass in `AccountType` and `BillingSchedule` types to `TAccount` and `IBilling` respectively so their use becomes more flexible in the `PersonalAccount` and `FreeBilling` classes as well as in the `IPersonalSubscription` type, where we are using enum members both as constant values as well as type definitions.

## Practical Applications of TypeScript Enums

Enums are super useful when you have to organize a set of related constants. Here are some real-world examples:

### 1. Role-Based Access Control

Let's say you're building an app with different user roles. You could use enums to define those clearly:

```typescript
enum UserRole {
  ADMIN = "Admin",
  EDITOR = "Editor",
  VIEWER = "Viewer",
}

function checkAccess(role: UserRole): void {
  if (role === UserRole.ADMIN) {
    console.log("You have full access!");
  } else {
    console.log("Limited access only.");
  }
}
```

### 2. HTTP Methods for APIs

You can use enums to standardize HTTP methods when making API calls:

```typescript
enum HttpMethod {
  GET = "GET",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE",
}

function makeRequest(method: HttpMethod, url: string): void {
  console.log(`Sending a ${method} request to ${url}`);
}

makeRequest(HttpMethod.POST, "/api/users");
```

### 3. Error Types

Enums are much cleaner for handling errors. You can define error categories like so:

```typescript
enum ErrorType {
  NETWORK = "Network Error",
  VALIDATION = "Validation Error",
  SERVER = "Server Error",
}

function logError(type: ErrorType): void {
  console.log(`An error occurred: ${type}`);
}

logError(ErrorType.NETWORK);
```

Enums will make your code more readable and save you from using invalid values. No longer magic strings or numbers everywhere!

## Common Pitfalls Using Enums

Here are some mistakes I've run into when working with enums—and how I avoid them now:

### Forgetting to Initialize String Enums

If you are using string enums, then all members must have an initial value. Man, I forgot this one all the time! Nowadays, I initialize them upfront.

```typescript
// Wrong
enum Color {
RED,
GREEN, // Error: Must have an initializer
}

// Correct
enum Color {
RED = "Red
GREEN = "Green,
}
```

### Hardcoded Values

Early on, I would hard-code values like "Admin" or 0, which became a nightmare to maintain. Nowadays, I always use enums for cleaner, centralized constants.

```typescript
// Instead of this:
const userRole = "Admin";

// Use this:
enum UserRole { ADMIN = "Admin,

EDITOR = "Editor,


}

const userRole = UserRole.ADMIN;
```

### Confusing Enum Keys and Values

I’ve definitely tried to use a value when a key was needed or vice versa. With numeric enums, you can go both ways, but string enums are more strict.

```typescript
enum Status {
  ACTIVE = "Active",
  INACTIVE = "Inactive",
}

console.log(Status.ACTIVE); // "Active"
console.log(Status["Active"]); // Error: Property 'Active' does not exist
```

### No Logging for Errors

Cron jobs used to fail silently until I started logging errors. The same principle applies to enums—you should always validate that the values you pass are correct, especially when enums interact with APIs or third-party systems.

### Overcomplicating Numeric Enums

I once manually assigned values to every member in a numeric enum. Turns out, you can let TypeScript auto-increment them for you:

```typescript
enum BillingCycle {
  FREE = 0,
  MONTHLY, // 1
  YEARLY, // 2
}
```

By avoiding these mistakes, my code has become more reliable and easier to debug! Enums are great when used well.

## Summary

In this post, we explored TypeScript enum concepts by storing groups of constants in a couple of enums defined to implement a simplistic tier based Subscription model. We stored constants in enums for `AccountType` and `BillingSchedule`. On our way, we found that it is mandatory to initialize every member in a string enum, and which is not necessary in a numeric enum. We saw how an uninitialized first member is automatically assigned an offset of `0` and subsequent members get auto-incremented by `1`. We learned how to assign offset values at any point in a numeric enum.

We also demonstrated how string enums implement unidirectional mapping and numeric enums implement a more convenient unidirectional mapping at compiltion and got an idea of typical objects introduced by them to runtime. We discussed the common usage of literal enum expressions in declaring string and numeric enums with constant values, and how they differ from computed member values.

Towards the end, we explored the types generated by the enums and leveraged them to derive our own subtypes. Finally we implemented a basic `PersonalSubscription` class that demonstrates the convenience offered by objects and types generated by TypeScript enums.
