In a TypeScript enum, what makes a member "computed" rather than "constant", and what does the compiler stop you from doing once a member is computed?
answer
- can the compiler fold it while checking?
- literals and arithmetic over earlier members
- a call is never foldable
- nothing to increment from afterwards
- const enum needs compile-time values
basics
~20 sA constant member is a literal or an expression the compiler can fold at compile time; anything else, such as a function call, is computed. A computed member must be numeric, forces the next member to have its own initializer, and is rejected inside a const enum.
solid answer
~50 sA member is **constant** when the compiler can evaluate it while checking: a numeric or string literal, a reference to an earlier constant member, or arithmetic and bitwise operations over those. Anything the compiler cannot fold — `"abc".length`, `Math.random()`, any call — is a **computed** member. Three things follow. First, a computed member must be of type `number`; a string-typed one fails with "Type 'string' is not assignable to type 'number' as required for computed enum member values". Second, the member immediately after a computed one must have its own initializer, since there is no known previous value to increment. Third, a `const enum` may not contain a computed member at all — you get "const enum member initializers must be constant expressions" — because inlining requires a value known at compile time. There is also an emit difference: constant expressions are folded, so `1 << 3` emits as `8`, while a computed member's expression survives into the output and runs at module evaluation.
code
typescript · 15 lines// Constant members — all foldable, so this enum could be `const enum`
enum Flags {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
ReadWrite = Read | Write,
}
// Computed member — legal, but the next member now needs its own value
enum Sizes {
Base = 'abcd'.length, // computed: expression survives into the emit
Double = 8, // required; auto-increment cannot follow a computed member
}
console.log(Flags.ReadWrite, Sizes.Base); // 3 4go deeper
Know that enum members are usually plain literals or auto-incremented, and that writing a function call as a member value is unusual and comes with extra rules.
Draw the line clearly: constant means the compiler can fold it during checking; computed means it cannot. Name the consequences — numbers only, the following member needs an initializer, and const enums reject it.
Point out that a computed member turns the enum into something evaluated at module load, so its values can depend on import order or environment — a real hazard in code that treats enums as constant tables.
Set the codebase rule: enum members stay constant so the values remain a compile-time contract, and anything genuinely derived from runtime state lives in a normal module-level object instead.
## Two kinds of member Every enum member is classified by the compiler as either **constant** or **computed**, and the classification is not cosmetic — it changes what the compiler will let you write, what it emits, and whether the enum can be `const`. ## What counts as a constant enum expression A member is constant if any of these hold: - It has no initializer at all and is therefore auto-incremented (or is the first member, defaulting to `0`). - Its initializer is a numeric or string **literal**. - Its initializer is a *constant enum expression*: a literal, a reference to a previously-defined constant member (of this enum or another), a parenthesized constant expression, a unary `+`, `-` or `~` applied to one, or a binary `+ - * / % << >> >>> & | ^` over two of them. ```ts enum Flags { None = 0, Read = 1 << 0, // constant Write = 1 << 1, // constant ReadWrite = Read | Write, // constant — references earlier members } ``` All four are constant, which is exactly why the bit-flag idiom works: the compiler folds every one of them. ## What computed means Anything the compiler cannot evaluate is computed: ```ts enum E { Len = 'abcd'.length, // computed — a property access Rand = Math.random(), // computed — a call } ``` A computed member is legal in a plain enum, and it is genuinely evaluated when the emitted module runs. But it carries three restrictions. ### Restriction 1 — computed members must be numbers The emitted object supports arbitrary expressions only for the numeric case, and the compiler enforces that: ```ts enum F { A = 'a'.toUpperCase() } // ~~~~~~~~~~~~~~~~~ error TS18033: // Type 'string' is not assignable to type 'number' // as required for computed enum member values. ``` String members must be literals. There is no such thing as a computed string enum member. ### Restriction 2 — the next member needs an initializer Auto-increment means "previous value plus one", and the compiler does not know a computed member's value, so it cannot compute a successor: ```ts enum Mixed { A = 'xy'.length, B } // ~ error TS1061: Enum member must have initializer. ``` This is the same diagnostic you get after a string member, and for the same underlying reason: there is nothing to increment from. ### Restriction 3 — const enums forbid computed members ```ts const enum Computed { A = 'abc'.length } // ~~~~~~~~~~~~ error TS2474: // const enum member initializers must be constant expressions. ``` A `const enum` exists to have its member references replaced by their literal values at each use site. That substitution is only possible if the compiler knows the value while compiling, so a computed member is a contradiction in terms there. ## The emit difference Constant expressions are folded away; computed ones are not. Compare the emitted body for: ```ts enum E { Len = 'abcd'.length, Next = 10, Shift = 1 << 3 } ``` ```js E[E["Len"] = 'abcd'.length] = "Len"; // expression survives E[E["Next"] = 10] = "Next"; E[E["Shift"] = 8] = "Shift"; // 1 << 3 folded to 8 ``` So `Shift` costs nothing at runtime while `Len` runs a real property access when the module is first evaluated. In practice this means a computed member can *depend on runtime state* — an environment variable, a call into another module — which quietly turns an enum from a compile-time constant table into something order-dependent at module load. That is a strong argument for keeping members constant. ## Practical guidance Use computed members sparingly, if at all. Their legitimate use is deriving a numeric value from something genuinely external, and even then a plain `const` object is usually clearer. Keep enum members constant and you keep every option open: the enum stays foldable, it can become a `const enum` later if you need the inlining, and its values do not depend on when the module happened to be evaluated. In an interview, the crisp answer is: constant means the compiler can fold it; computed means it cannot. Then name the three consequences — numbers only, the next member needs an initializer, and `const enum` rejects it outright — and mention that constant expressions like `1 << 3` are folded into the output while computed expressions survive.
- Why does `1 << 3` not appear anywhere in the emitted JavaScript?Because it is a constant enum expression, so the compiler evaluates it during checking and writes the folded result — the emit contains `8`. That folding is what lets constant members be referenced by later members, participate in bit-flag arithmetic, and be inlined if the enum is later declared `const enum`. A computed initializer cannot be folded, so its expression is emitted verbatim and runs at module evaluation.
- Is `Math.random()` a legal enum member initializer?In a plain enum, yes — it is a computed member of type `number`, and it really is evaluated once when the module is first evaluated. It is a bad idea, because the enum's values then differ per process, but it compiles. In a `const enum` it is rejected: member initializers there must be constant expressions the compiler can inline.
saying these in an interview costs you the question
- Thinks any expression is allowed as an enum initializer
- Believes 1 << 3 is a computed member because it is an expression
- Expects a string-returning call to work as a member value
- Assumes const enums accept the same initializers as plain enums
- Says computed initializers are evaluated at compile time