Typescript enums drawbacks and solutions
Tags: typescript, enum, readonly, optimization
Published: 2021-12-22 12:12:21

---
title: Typescript enums drawbacks and solutions
published: true
description: Typescript enum feature brings enums to the language. In this article we'll see its drawbacks and how we can do better.
tags: typescript, enum, readonly, optimization
//cover_image: https://direct_url_to_image.jpg
---

Typescript has for years an enum feature that is very similar to the C# one. However, when run, Typescript is Javascript and enum have to be transpiled. The result is not so satisfying than what we could expect as it's very verbose. In this article we'll see why it's so verbose and what other solutions can improve our code.

# The problem of TS enums

Typescript's enums are very handy because there's nothing like enum yet in Javascript, probably because a JS object is very close from an enum already. But when we write TS code, the enum informs about readonly state, which is something that can't be done easily from the object (well this is not true anymore... read further).

However, at build time, the TS enum has to be transpiled to an object. The result will be a very verbose transpiled output for something that could be simply an object structure. The developer has then to choose between a lighter output by using an object or a better typing comfort by using the enum type...

Here's a concrete example of enum transpilation in TS 
_(TSConfig's target = es2017)_

```typescript
export enum EHttpStatusCode {
    Ok = 200,
    BadRequest = 400,
    Unauthorized = 401,
    Forbidden = 403,
    NotFound = 404,
    ServerError = 500,
}
```
will transpile to the following JS
```javascript
export var EHttpStatusCode;
(function (EHttpStatusCode) {
    EHttpStatusCode[EHttpStatusCode["Ok"] = 200] = "Ok";
    EHttpStatusCode[EHttpStatusCode["BadRequest"] = 400] = "BadRequest";
    EHttpStatusCode[EHttpStatusCode["Unauthorized"] = 401] = "Unauthorized";
    EHttpStatusCode[EHttpStatusCode["Forbidden"] = 403] = "Forbidden";
    EHttpStatusCode[EHttpStatusCode["NotFound"] = 404] = "NotFound";
    EHttpStatusCode[EHttpStatusCode["ServerError"] = 500] = "ServerError";
})(EHttpStatusCode || (EHttpStatusCode = {}));
```

Pretty verbose isn't it?

## Why does Typescript generate all this code?

The reason for that is [Reverse Mapping](https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings). In short, the reverse mapping allows you to pass the value to the enum to get a literal value.

The good point of this reverse mapping is that if it was not provided, but we would need it, then we would have to write some logic like following:

```typescript
function getEnumKeyByEnumValue<T extends {[ index: string ]: string}>(myEnum: T, enumValue: string): keyof T |null {
    const keys = Object.keys(myEnum).filter(x => myEnum[x] == enumValue);
    return keys.length > 0 ? keys[0] :null;
}
```

There's a cavehat tho, and it's that most of the time we don't need this reverse mapping... So we're basically bloating the bundle with useless code more than we should.

## String enums have no reverse mapping

Something interesting to know is that string enums are handled differently by the compiler. If you compare a string enum and another type of enum (ex: number), you'll quickly notice that the transpiled code is different for the string value. It's now a simple enum without reverse mapping, but still it's more verbose than a basic javascript object.

So if you were trying to use reverse mapping on a string enum, then now you know why your code is not working as expected.

# Alternatives to enums
Verbosity of the transpilation is something that was heard by the TypeScript team and they now offers alternatives:

## The const assertion feature

The [const assertion](
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) feature will help us keep a lighter bundle, but at the same time it will give us the same typing feature than the enum. 

This is how to use it:
1. declare the object like you would in plain JS
1. add `as const` after the declaration

```typescript
export const httpStatusCode = {
    Ok: 200,
    BadRequest: 400,
    Unauthorized: 401,
    Forbidden: 403,
    NotFound: 404,
    ServerError: 500,
} as const;
```
What this declaration will do, is create the object, but also inform TS that all the properties and the object are readonly.
Talking about the output bundle, the Javascript transpiled result will be exactly the same but without the `as const` statement for sure.

### However we still miss the typing feature

So now yes we have the enum declaration, but we still miss the Type which will be used by TS to type check our code.

For TS check, we will need 1 more declaration (here below it's an example of how you could try to declare an enum property, but...)

```typescript
class HttpResponse {
 code: HttpStatusCode = 200;
// other stuff here
}
```

This won't work because our object is a value and we can't directly use it as a type. So we will need to extract the type from it:
```typescript
type HttpStatusCodeKey = keyof typeof httpStatusCode;
export type HttpStatusCode = typeof httpStatusCode[HttpStatusCodeKey];
```

Now we have the real type which is representing the union of the `httpStatusCode` object's values. And the good news is that this type is only used for building TS and is then removed from the transpiled output. So the only thing JS will have is the object.

On TS side it will then be very easy to type the code like
```typescript
class HttpResponse {
 code: HttpStatusCode = httpStatusCode.Ok;
// other stuff here
}
```

Also notice the casing convention. The object const is camelCased (`httpStatusCode`) because it's a value. On the other hand, the type extracted from the object is PascalCased (`HttpStatusCode`). So it follows usual convention and is then easy to differentiate type from value object.

## Const enums

The [const enum](https://www.typescriptlang.org/docs/handbook/enums.html#const-enums) is exactly the same syntax than a standard enum, but with a const prefix.

```typescript
export const enum EHttpStatusCode {
    Ok = 200,
    BadRequest = 400,
    Unauthorized = 401,
    Forbidden = 403,
    NotFound = 404,
    ServerError = 500,
}
```

However this slight difference will become a huge one in the transpiled output. See here what will come out:

```typescript

```

You read it correctly, there's nothing... The reason for that is that the whole enum is now virtual. And when you will use its value, the value will directly be printed instead. It's like a swipe of the enum to a value at compile time.

# Conclusion

Typescript enum is a C# flavor brought to Typescript. It's an innocent syntax when you're coming from C# code, but it can be harmful to the end user. The typescript authors are also aware of this pitfall and are doing great work to provide alternate solutions. So if you don't know what to use you can follow this logic:

1. Do you need reverse mapping? (If you don't know, then the answer is probably "No")
- If Yes, and your enum is not about strings, then you'll have to use a standard enum
- If No, continue at point 2
2. Do you need to loop over enum members?
- If Yes, then use const assertion
- If No, continue at point 3
3. Use const enum and live happy 

Feel free to share your thoughts in the comment 💬 or to like 👍  the post if it was interesting for you.