8 ms·
After almost a decade of TypeScript my recommendation is to not use TypeScript enums. Enums is going to make your TypeScript code not work in a future where Ty
by msoad 2y ago
After almost a decade of TypeScript my recommendation is to not use TypeScript enums.
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]
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`
All of the examples in the article can be achieved without enums. See 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 https://www.typescriptlang.org/play/?#code/PTAEFEA8EMFsAcA2B...
[1] https://github.com/tc39/proposal-type-annotations https://github.com/tc39/proposal-type-annotations
- diggan 2y ago> 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] How is that the conclusion you reach? The proposal you link says types will be treated like comments by the runtime, so it's not about adding types that will be used in the runtime (which begs the question, why even add it? But I digress), but about adding types that other tooling can use, and the runtime can ignore. So assuming the runtime will ignore the types, why would using enums specifically break this, compared to any other TypeScript-specific syntax?
- msoad 2y agoThe idea from the proposal is that types are used by other tools to type-check and runtimes would ignore them. It's not final yet but it's very likely that in future you can `node -e 'function foo(arg: string) {}; foo(42)'` but if your code has `enum` in it, Node.js or browser will throw an error
- diggan 2y agoBut it's not specifically about enums, but anything from TS that generates code. You would need to stop using enums, parameter properties, namespaces (called out by the proposal) and probably more. Seems weird to me to decide you're OK with the build step and all the other complexity TS adds, but using enums is too much, because maybe in the future JS runtimes might be able to strip away types for you without a build-step. But we all have different constraints and use cases, I suppose it does make sense for what you're building.
- cbovis 2y agoIt's not a hypothetical, it's here in Node 23: https://nodejs.org/docs/latest/api/typescript.html#typescript-features https://nodejs.org/docs/latest/api/typescript.html#typescrip....
- diggan 2y agoSpeaking about the specification, it's a proposal. Yes, some run ahead and implement proposals under experimental flags, doesn't make it any more/less hypothetical as the proposal can still be rejected rather than progressing.
- mistercow 2y agoCome on, now. > maybe in the future JS runtimes might be able to strip away types for you without a build-step. You can't backpedal from that to "speaking about the specification". It's not future JS runtimes. It's a thing you can take advantage of right now.
- bogdan 2y agoYou're correct. Nodejs can already run typescript code directly but it only does type stripping so it won't work with enums or namespaces which need additional code generated at build time.
- Klaster_1 2y agoOften, I find myself in need to find all references of "Active" from your example, which doesn't work with union values. This looks like a LSP limitation. Of course, you can move assign values into consts and union these instead. But that means you are half way there to custom run-time enums, and all the way after you wrap the consts with an object in order to enumerate over values at run-time.
- mistercow 2y ago> Often, I find myself in need to find all references of "Active" from your example, which doesn't work with union values. I'm able to do that just fine in VS Code / Cursor. I set up a union like this: export type TestUnion = 'foo' | 'bar' | 'baz'; Then use it in another file like this: const bar: TestUnion = 'bar'; const barString: string = 'bar'; If I select 'bar' from the type and choose "Go to references", it shows me the `const bar` line, but not the `const barString` line, which is what I would expect.
- homebrewer 2y agoUse `const enum Foo`, they leave no traces in the transpiled JS and provide good IDE experience.
- baq 2y agoMaybe argue for enum being added to ecmascript instead?
- mistercow 2y agoBut why? The feature offers almost no benefit in TS at this point over other existing features, has no function in JS other than TS compatibility, and is increasingly flaky in TS itself. Adding more complexity to JS rather than simplifying TS by deprecating this old, janky foot gun and educating devs on better alternatives seems like moving in the wrong direction.
- zarzavat 2y agoTypeScript didn't invent enums. They exist because it really sucks to write out: const MyEnum = { x: 1, y: 2, z: 3, // etc } instead of enum MyEnum { x = 1, y, z, // etc } when you want a series of constants each with a unique value but don't particularly care what that value is. TypeScript's enums are particularly weak compared to enums in other languages precisely because there's no JS support for enums. Modern languages have support for ADTs.
- mistercow 2y agoTypeScript enums were added in large part because union types didn't exist at the time. Those don't require you to write out anything like the above. The only case where you would need to write out number literals like that would be if you specifically wanted the values to be numbers for some reason, rather than interned strings. In the vast majority of cases, there's no good reason to do that. Edit: But no, the reason enums in TypeScript suck is not that JS doesn't have them. That wouldn't fix anything other than the type stripping problem. The main reason that they suck is that they use a completely different type model from the rest of the language.
- zarzavat 2y agoenums are a feature of most programming languages. It doesn't matter to me why TypeScript had to add them, just like it doesn't matter why JS has functions or if statements. 90% of the enums I use are regular integer enums. I don't get much use out of string enums, as you say union types do that job just fine.
- girvo 2y agoAgreed. Its one of my major annoyances with Relay, is that it generates enums.
- rvz 2y ago[flagged]
- Vinnl 2y agoI can assure you that I can find a way to shoot myself in the foot in any language.
- rvz 2y agoWith any language, you can (which isn't my point). The point is which one is the easiest and its with anything in proximity of the whole JavaScript ecosystem including TypeScript. It really says a lot about how immature it is especially for backend and just by even hearing the complaints about TypeScript 'enums' tells me all I need to know. So what other foot-guns have the JS / TS ecosystem have hidden?
- deleted 2y ago[deleted]
- n144q 2y agoDude, why are you here? Your comment is just a (very opionated) rant and not providing any value for anybody in the discussion.
- FjordWarden 2y agoI understand, but what if I want to use the enums the way they are used in C, as a label for a number, probably as a way to encode some type or another. Sum types of literal numbers are not very practical here because the labels should be part of the API.
- anamexis 2y agoIn that case you can just use object literals `as const`.
- mistercow 2y agoWhat in your view is the downside to doing this? export const MyEnumMapping = { active: 0, inactive: 1 } as const export type MyEnum = typeof MyEnumMapping[keyof typeof MyEnumMapping]; So you have the names exposed, but the underlying type is the number.
- akdev1l 2y agoThis is way harder to parse and understand than the enum alternative. Personally I am definitely not skilled enough at typescript to come up with this on my own before seeing this thread so this was not even an option until now.
- mistercow 2y agoWell, that's basically how you would have done it in vanilla JS before typescript came around. The main awkwardness is the type definition. I often prefer to use a library like type-fest for this kind of thing so you can just say: export type MyEnum = ValueOf<MyEnumMapping>; TypeScript not having enough sugar in its built-in utility types is definitely a fair criticism. But more to the point, the above is not usually how you do enums in TS unless you have some very specific reason to want your values to be numbers at all times. There are some cases like that, but usually you would just let the values be strings, and map them to numbers on demand if that's actually required (e.g. for a specific serialization format).
- 2y ago
- madeofpalk 2y ago> in a future where TypeScript code can be run with Node.js FYI, this is now. Node 23.6 will just run typescript files than can have their types stripped https://nodejs.org/en/blog/release/v23.6.0#unflagging---experimental-strip-types https://nodejs.org/en/blog/release/v23.6.0#unflagging---expe.... There is a seperate --experimental-transform-types flag which'll also transform for enums, but no idea if they ever intend to make this not experimental or unflagged.
- plopz 2y agoI think the biggest hurdle in getting something like that to work is how typescript handles the import syntax
- madeofpalk 2y agoyou just tell typescript to stay away from import syntax, and use node-native resolution and it all just works. its 2025 and node is finally good :)
- dunham 2y agoIt would be nice if node did tail call optimization, but that seems unlikely at this point (v8 added and then removed it). I've been using bun as a backend for my toy language because of this.
- WorldMaker 2y agoMost of the "drama" in recent Typescript, such as requiring file extensions, with the import syntax has been aligning with the Browser/Node requirements. If you set the output format to a recent enough ESM and the target platform to a recent enough ES standard or Node version it will be a little more annoying about file extensions, but the benefit is that it import syntax will just work in the browser or in Node. The only other twist to import syntax is marking type-only imports with the type keyword so that those imports can be completely ignored by simple type removers like Node's. You can turn that check on today in Typescript's compile options with the verbatimModuleSyntax [1] flag, or various eslint rules. [1] https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax https://www.typescriptlang.org/tsconfig/#verbatimModuleSynta...
- rererereferred 2y agoDoesn't typescript already work with Deno and Bun? How do they do it?
- msoad 2y agoby compiling it, which opens a huge can of worms. Deno relies on tsconfig.json configurations for instance
- WorldMaker 2y agoDeno bundles a full LSP that will do compilation using its (modified) tsconfig.json-like configurations, but Deno's type remover at runtime is fairly dumb/simple and I believe a simple Rust implementation. Part of what you can't configure in Deno's tsconfig.json-like configuration files are things that keep the type remover simple (such as turning enums back on).
- n144q 2y agoThat's a stage 1 proposal that has barely gained any traction since its release. In fact it hasn't been updated for quite a while (with "real" content changes). I wouldn't make decisions for my current code based on something that probably will never happen in the future. https://github.com/tc39/proposal-type-annotations/commits/main/README.md https://github.com/tc39/proposal-type-annotations/commits/ma...
- runarberg 2y agoTC-39 is kind of lame at the moment. I can’t imagine they will stay this lame forever. There are some reasonable voices inside TC-39, so even thought currently the lame voices at the committee are more powerful, that could change at any moment.
- eyelidlessness 2y agoIt’s moving slowly, but I think it’s almost inevitable. Type annotations are generally gaining or maintaining their already widespread popularity, and bringing them into the language syntax would just be an acknowledgment of that fact. I think the only thing that might hold that back is the proposal’s commitment to non-TypeScript use cases, which while magnanimous is a huge opportunity for the kinds of bike shedding that might tank a proposal like it.
- throwitaway1123 2y ago> Enums is going to make your TypeScript code not work in a future where TypeScript code can be run with Node.js Apparently they're planning on adding a tsconfig option to disallow these Node-incompatible features as well [1]. Using this limited subset of TS also allows your code to compile with Bloomberg's ts-blank-space, which literally just replaces type declarations with whitespace [2]. [1] https://github.com/microsoft/TypeScript/issues/59601 https://github.com/microsoft/TypeScript/issues/59601 [2] https://bloomberg.github.io/ts-blank-space/ https://bloomberg.github.io/ts-blank-space/
- WorldMaker 2y agoThose flags have already started to show up in today's typescript: verbatimModuleSyntax [1] and isolatedModules [2], for instance. [1] https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax https://www.typescriptlang.org/tsconfig/#verbatimModuleSynta... [2] https://www.typescriptlang.org/tsconfig/#isolatedModules https://www.typescriptlang.org/tsconfig/#isolatedModules
- throwitaway1123 2y agoThose definitely help, but the proposed erasableSyntaxOnly flag would disallow all features that can't be erased. So it would prevent you from using features like parameter properties, enums, namespaces, and experimental decorators. It would essentially help you produce TypeScript that's compatible with the --experimental-strip-types flag (and ts-blank-space), rather than the --experimental-transform-types flag, which is nice because (as someone else in this thread pointed out), Node 23 enables the --experimental-strip-types flag by default: https://nodejs.org/en/blog/release/v23.6.0#unflagging---experimental-strip-types https://nodejs.org/en/blog/release/v23.6.0#unflagging---expe...
- WorldMaker 2y agoAlso worth noting that the eslint rules for what `erasableSyntaxOnly` might be are already well established, for those looking to do it today.