One thing I'm missing in the comments here is that enums are a very early TypeScript feature. They were in there nearly from the start, when the project was still trying to find clarity on its goals and principles.<p>Since then:<p>- TypeScript added string literals and unions, eg `type Status = "Active" | "Inactive"`<p>- TypeScript added `as const`, eg `const Status = { Active: 0, Inactive: 1 } as const`<p>- TypeScript adopted a stance that features should only generate runtime code when it's on a standards track<p>Enums made some sense back when TS didn't have any of these. They don't really make a lot of sense now. I think they're effectively deprecated, to the point that I wonder why they don't document them as deprecated.
After almost a decade of TypeScript my recommendation is to not use TypeScript enums.<p>Enums is going to make your TypeScript code not work in a future where TypeScript code can be run with Node.js or in browser when typings are added to JavaScript[1]<p>Enums results in runtime code and in most cases you really want type enums. Use `type State = "Active" | "Inactive"` and so on instead. And if you really want an closed-ended object use `const State = { Active: 1, Inactive: 0 } as const`<p>All of the examples in the article can be achieved without enums. See <a href="https://www.typescriptlang.org/play/?#code/PTAEFEA8EMFsAcA2BTUBGAXKAqgZwJYB2A5qAK6H4D2hoALgJ7zK6hG53LQAmoVAZqGSEysXAChGzUAGU60TqAC8oAEQBBAMZ18AN2SrQAHzUBJQtG16DAbnHj+FKzVCIqxOQuQAKDl6yenACUoADe4qCRoJo0uFQoAHRuxN4ABgAqABaommQATnnCdKB+ivi4WAAkoaXIAL6pQXZ19iA4uNDEyOLJgT4aVvqqTT3ufd6qAArC3ETEwzagbeAFVHlY6UyoAORTM3Oq22yshFTF0LgExBYARij0VPRboNt92wmtYFBwSKgATFg8HNQNBorF5IRilQbgArZDaUAAd3wdEyoFSFzBhA4qXEMWxxT6yjCESiWh0+iwAwpBgANKTIuZLDSqUzBnTxHUQax8Rw7JJnn0ANLIBisFQAa1FAiezBlfUWbWp1kMJlUbJpqgF0j6ADVoIgyCxid4pMh5fJggBtYWi3AAXUVYGVQ2MZgs7K1DicOhcvUtyAAYnkqLAAPKw+F0XwBgIB-WGlghcJRLFxRLJNJZHL5QqQkoB45VGoBhojFriNp4Trdf1eYOhiNw7TePoJcnWEZ1zgN8ORlt7QizEgLJZfVbrUCbaS7aZDg5HcqgU7nS74a7QO6oOiPM0vF0GN3qj2a96fCAwBD3ADMWAAcqJkHl8JohCJYKBCkhLMhYEU8eCoAPn+z6aESKgpmS7JYGg9Kphq1hYAADPSXKYrydD8nuwFPi+eoGka4qgKaWwyjhoF9EEVpSgwMpmmRj4UQGjpjugbpIfYjiEM4tDJOReEBjG-hAYxAleAmhHJgyabxMgSTuFm2TLqJr61EWoDVLUZbNOe1ZdKMxD8WBglGW2Hb6F27imYJfxNKxKwhpO047H8i4nGc3JXLc9w7rKOxIW6aBnpWXyXr8oAACxYAAstA8DwMC764a+awFs+JCgLoBHGr5mSiNAhAALSFDwm73BYf4SBh7RPgASrJxKQZE6jcLARBUuoAAi0WmHeqhwVEeBPlS2AyOAtX9dJADihF0FSU3YOAMjpJNaE8uCWHPENeT1SgEnGioJFyoI227cgVE0XRpEnbgdWySxSpdT1fVHqN42qmoC1LStnE+tQtBdHQp2yd4IYoICt07bJ+24FJqa4MidCaGioOyXDqZRJoFyoMDiQtW1hAYNJGNRIUdD5LQGitUQ5R0HkChrKodgk5EWO3TVUOJNtRMsxjZMU2onhDtAeS8NtTPE5j2Mc2dCQzSwc2SyT-N5JT8scBzEupi0FZVh0+m8rJ8kpIDuM+Gb7bU4QQR2W0+M0xw9M7nkAHYkbmam5DZ0TGGADqd7vTbToQBOGzPLsfsBxNbnLh5FxeWV267uHGjdb1H2qG9E1Hl9y2HB8IUXj89wAKxYJ1DAVS+oA0awZAEBlqSXfwqQgkOfklNA-DIIwrsawAwvEayxfAjXSbVyDcFSADEgaBkhC9IZNqZTYUwgzwvc8L8vUQAEKJhvi9z6t3Jpph9h7oPbh5ERzd+TKV-D3FweqBP3AZ6vyDCBn+9Gl6XE8VAIDR+eQAASyBIDeBiNfLAIDYYklTCrWgICR5WmgWsR0nJdL626IbDMClgFDzARAiYb9hi2zALPeei8+7uwIT3EB4DIGqAAJrIEQG4RE5Dg4OTWGHGcrD2GcMOMcWOq4E5bgeB3XYZCc5r0ID-RM+dxBAA" rel="nofollow">https://www.typescriptlang.org/play/?#code/PTAEFEA8EMFsAcA2B...</a><p>[1] <a href="https://github.com/tc39/proposal-type-annotations">https://github.com/tc39/proposal-type-annotations</a>
I use TypeScript in a way that leaves no TS traces in compiled JS. It means no enums, no namespaces, no private properties, etc.<p>Great list of such features: <a href="https://www.totaltypescript.com/books/total-typescript-essentials/typescript-only-features" rel="nofollow">https://www.totaltypescript.com/books/total-typescript-essen...</a><p>TS has a great type system, the rest of the language is runtime overhead.
The suggested alternative looks overly complex to me.
Moreover, it uses the `__proto__` property that is deprecated [0] and never was standardized.
I could write something like this instead:<p><pre><code> type MyEnum = typeof MyEnum[keyof typeof MyEnum];
const MyEnum = {
A: 0,
B: 1,
} as const;
</code></pre>
Unfortunately I found it still more verbose and less intuitive than:<p><pre><code> enum MyEnum {
A = 0,
B = 1,
}
</code></pre>
TypeScript enum are also more type-safe than regular union types because they are "nominally typed": values from one enum are not assignable to a variable with a distinct enum type.<p>This is why I'm still using TypeScript enum, even if I really dislike the generated code and the provided features (enum extensions, value bindings `MyEnum[0] == 0`).<p>Also, some bundlers such as ESbuil are able to inline some TypeScript enum. This makes TypeScript enum superior on this regard.<p>In a parallel world, I could like the latter to be a syntaxic sugar to the former.
There were some discussions [1] for adopting a new syntax like:<p><pre><code> const MyEnum = {
A: 0,
A: 1,
} as enum;
</code></pre>
[0] <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto" rel="nofollow">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...</a><p>[1] <a href="https://github.com/microsoft/TypeScript/issues/59658">https://github.com/microsoft/TypeScript/issues/59658</a>
We alway use this in place of ENUMs:<p><pre><code> export const SMS_TYPE = {
BULK: 'bulk',
MARKETING: 'marketing',
PIN: 'pin',
SIGNUP: 'signup',
TRANSACTION: 'transaction',
TEST: 'test',
} as const;
export type SmsType = typeof SMS_TYPE[keyof typeof SMS_TYPE];
</code></pre>
ENUMs (at least in my experience, which may be dated) had a number of drawbacks that pushed us to this format. I vaguely remember having issues parsing data from the server and/or sending ENUM values to the server but it's been a long time and I've been using this const pattern for around 5 years or so now.
I personally see TS enums as an anti-pattern.<p>One big reason: you can't name it interfaces.d.ts, or import as type, which has widespread implications:<p>Your types are now affecting your shipped bundles.<p>Sure that's a small bit of size - but it can actually lead to things like server side code getting shipped to the client.<p>Whereas if it's all .d.ts stuff you know there's no risk of chained dependencies.<p>I'd go so far as to say default eslint rules should disallow enums.
I like how this article demystifies TypeScript enums—especially around numeric vs. string values and all the weird runtime quirks. Personally, I mostly steer clear of numeric enums because of that dual key/value mapping, which can be as confusing as Scala’s old-school Enumeration type (where numeric IDs can shift if you reorder entries). In Scala, it’s often better to use sealed traits and case objects for exhaustiveness checks and more explicit naming—kind of like TS’s union-of-literal types.<p>If you just need a fixed set of constants, union types with never-based exhaustiveness checks feel simpler and more “ADT–style.” That approach avoids generating the extra JS code of enums and plays nicer with certain “strip-only” TypeScript setups. In other words, if you’ve ever regretted using Enumeration in Scala because pattern matching turned messy or IDs moved around, then you’ll probably want to keep TypeScript enums at arm’s length too—or at least stick to string enums for clarity.
I think type-level string unions are the way to go. They're concise, efficient (the strings are interned anyway), and when you're debugging you know what the values are rather than getting mysterious integers.
For someone who writes TS only occasionally and mostly doesn't care about the JS ecosystem, this is a great article. I picked up a few tricks. That said, normalization of warts is a common thing in JS, and people tend to just live with it rather than fix it. This feels like another example of that.<p>In Go, if something is discouraged (unsafe, runtime, reflection shenanigans), you immediately know why. The language is mostly free of things that exist but you shouldn’t use.<p>TS was a breath of fresh air when it came out. I never took Node seriously for backend work—it was always something I reluctantly touched for client-side stuff. But TS made some of JS’s warts bearable. Over time, though, it’s added so many crufts and features that these days, I shudder at the thought of reading a TS expert’s type sludge.
A particularly ugly but useful feature of "const enum" (sadly, the "const" flavor of enums are not referred to in this documentation), is that it's the only way to declare a compile-time constant in TypeScript.<p>e.g. for "development" vs "production" environments, you could write a declaration file for each of those envs as such:<p><pre><code> // production.d.ts
declare const enum ENVIRONMENT {
PROD = 0,
DEV = 1,
CURRENT_ENV = PROD,
}
</code></pre>
And then write in your code something like:<p><pre><code> // some_file.ts
if (ENVIRONMENT.CURRENT_ENV === ENVIRONMENT.DEV) {
// do something for dev builds
}
</code></pre>
It will be replaced by TypeScript at compile-time and most minifiers will then be able to remove the corresponding now-dead code when not in the right env.<p>This is however mainly useful when you're a library developer, as you may not have any "bundler" dependency or any such complex tool able to do that task.<p>Here, the alternative of bringing a complex dependency just to be able to replace some constants is not worth its cost (in terms of maintenance, security, simplicity etc.), so even if `const enum`s may seem poorly-adapted, they are actually a good enough solution which just works.
Ts enums are unofficially deprecated.<p>I remember being shocked about it when i heard that on ts-congress by, Nathan Sanders a ts contributor, around 4-5 years ago.<p>I find the ts-enums incredibly poorly designed and advice my juniors to stay away from them generally.<p>It's almost similar for interface (<a href="https://shively-sanders.com/types-vs-interfaces.html" rel="nofollow">https://shively-sanders.com/types-vs-interfaces.html</a>)
Had a quick look but I was surprised to see using a Set.<p>Personally I use a plain string union. If I need to lookup a value based on that I’ll usually create a record (which is just a stricter object). Typescript will error if I tried to add a duplicate.<p>This is all enforced at build time, whereas using a Set only happens at runtime.<p><pre><code> type Fruit = ‘apple’ | ‘banana’;
const lookup: Record<Fruit, string> = { ‘apple’: ‘OK’, ‘banana’: ‘Meh’ }
</code></pre>
Unions are a more more universal syntax than enums.<p>It isn’t forced to be a 1:1 map of string to string; I’ll often use string to React components which is really nice for lots of conditional rendering.<p>On a slightly related topic, I also feel that the ‘type’ keyword is far more useful and preferable than ‘interface’. [1]<p>[1]: <a href="https://www.lloydatkinson.net/posts/2023/favour-typescript-types-over-interfaces/" rel="nofollow">https://www.lloydatkinson.net/posts/2023/favour-typescript-t...</a>
I've found this to be quite ergonomic and functional for handling string enumerations (including completeness in switches and record definitions):<p><a href="https://github.com/photostructure/fs-metadata/blob/main/src/string_enum.ts">https://github.com/photostructure/fs-metadata/blob/main/src/...</a><p>Usage:<p><pre><code> export const Directions = stringEnum("North", "South", "East", "West")
export type Direction = StringEnumKeys<typeof Directions>
</code></pre>
(I haven't published this as a discrete npm package--IMHO you should copy and paste this sort of thing into your own tree).
After several iterations (some of which older than TS native enums if I remember well), this is the Enum code I ended up with. It creates type, "accessors" (`MyEnum.value`), type guard (`isMyENum(...)`) and set of values (`for(const value of MyEnum)`), and have 2 constructor to allow easier transition from TS native enum.<p><a href="https://gist.github.com/forty/ac392b0413c711eb2d8c628b3e769896" rel="nofollow">https://gist.github.com/forty/ac392b0413c711eb2d8c628b3e7698...</a>
This article could be an unintentional case study in why letting patterns emerge beats designing them upfront. Java devs insisted on enum classes while JS devs gravitated towards plain objects tells us something about language evolution.<p>Makes me wonder if it was a mistake to include them at all instead of letting the community converge on patterns naturally, like we did with so many other JS patterns.
I'd advise against using TS enums nowadays because they conflict with a few useful flags (such as "isolatedModule" or Node type stripping).<p>A good tsconfig: <a href="https://gist.github.com/cecilemuller/80fed1b963171ca4e117f6d1e2a4a5a7" rel="nofollow">https://gist.github.com/cecilemuller/80fed1b963171ca4e117f6d...</a>
I dislike TypeScript enums for two reasons:<p>1. They have a runtime representation unlike most of the rest of TypeScript<p>2. They follow nominal typing instead of structural typing, again unlike the rest of TypeScript<p>IMO it's best to use string union types instead of enums. If you need to map that to another representation you can use a function or a record.