In TypeScript, given `enum Direction { Up = 1, Down, Left, Right }`, what value does each member hold — and why does `enum Level { Low = 'LOW', High }` fail to compile?
answer
- values come from position, not names
- previous member plus one
- the first bare member is zero
- no successor exists for a string
- explicit initializer required after a string member
basics
~20 sNumeric enum members auto-increment from the previous value, so Up = 1 makes Down 2, Left 3 and Right 4. String members never auto-increment, so any member following a string member must have its own explicit initializer.
solid answer
~50 sA numeric enum member written without an initializer takes the previous member's value plus one, and the very first member defaults to `0`. So `Direction { Up = 1, Down, Left, Right }` yields 1, 2, 3, 4, while `enum Status { Active, Done }` yields 0 and 1. Writing another explicit initializer restarts the sequence from that point. String enums have no equivalent rule — there is no "next string" to compute — so the compiler requires every string member to be initialized, and `enum Level { Low = 'LOW', High }` fails with "Enum member must have initializer". The same error applies to a bare member that follows a string member in a mixed enum. In practice I write string enums out in full, and I pin explicit numbers on any numeric enum whose values get persisted, so that inserting a member later cannot silently renumber the rest.
code
typescript · 9 linesenum Code {
Ok = 200,
Created, // 201
BadRequest = 400,
Unauthorized, // 401
}
console.log(Code.Ok, Code.Created, Code.BadRequest, Code.Unauthorized);
// 200 201 400 401go deeper
Be ready to compute the values on the spot: the first bare member is 0, and every other bare member is the previous one plus one. Say plainly that each string member needs its own value.
Explain that an explicit initializer restarts the counter for everything after it, and that a bare member following a string member is rejected because strings have no successor to increment.
Show judgment about persistence: pin numbers explicitly, or prefer string enums, whenever the values reach a database, a wire format or a stored log, so a later insertion cannot renumber existing data.
Own the contract question — decide whether enum values are an internal detail or part of a published schema, and set the rule for who may change them once other systems have stored them.
## What an enum declaration is An `enum` declares a named set of members and, unlike almost everything else in TypeScript's type layer, it is **not** erased at compile time — it emits a real JavaScript object. That means each member has an actual runtime *value*, and the rules for where that value comes from are worth knowing precisely. A member gets its value in one of two ways: you write an initializer (`Up = 1`), or you leave it bare (`Down`) and the compiler computes one. ## The numeric auto-increment rule The rule is simple and mechanical: - A bare **first** member is `0`. - Any other bare member is *the previous member's value plus one*. ```ts enum Status { Active, Done } // Active = 0, Done = 1 enum Direction { Up = 1, Down, Left, Right } // 1, 2, 3, 4 ``` Starting at `0` is the default that trips people up most often — many candidates assume the sequence starts at 1, which is why `enum Direction { Up = 1, ... }` is such a common idiom in real code: the author wanted 1-based values and had to say so. ## Restarting the sequence The counter is not global to the declaration; it resumes from whatever the previous member ended up as. So you can restart it as many times as you like: ```ts enum Code { Ok = 200, Created, // 201 BadRequest = 400, Unauthorized, // 401 } ``` This is genuinely useful for grouped codes, and it is also the reason a careless edit is dangerous: inserting `Accepted` between `Ok` and `Created` shifts `Created` from 201 to 202. ## Why string members cannot auto-increment There is no successor operation on strings. "LOW" plus one is not a value the compiler could invent, so TypeScript simply requires an initializer for every string member: ```ts enum Level { Low = 'LOW', High } // ~~~~ error TS1061: Enum member must have initializer. ``` The error is reported on `High`, not on `Low` — the declaration of a string member is fine; what is impossible is the *bare member that follows it*. This matters for reading the diagnostic correctly: the compiler is telling you "I cannot derive this one", not "string enums are illegal". The same rule fires in a mixed ("heterogeneous") enum. TypeScript permits mixing numeric and string members in one declaration: ```ts enum Het { No = 0, Yes = 'YES' } // legal enum Bad { No = 0, Yes = 'YES', Maybe } // error on Maybe ``` Mixing is legal but generally discouraged, because the resulting object has members of two different value types and consumers can no longer make simple assumptions about what a member is. ## The stability consequence Auto-increment couples a member's value to its *position* in the source. That is harmless while the values live only inside one running program. It becomes a data-integrity problem the moment those values leave the process: written to a database column, sent over the wire, stored in a log, or persisted in a browser's local storage. ```ts // v1 — rows in the DB store 0, 1, 2 enum Role { Viewer, Editor, Admin } // v2 — someone inserts a member alphabetically enum Role { Admin, Editor, Viewer } // every stored row now means something else ``` Nothing in the type system catches this: the code still compiles, and the old rows still hold valid numbers. The defence is to pin every value explicitly (`Viewer = 0, Editor = 1, Admin = 2`) or, better, use a string enum whose values are self-describing and cannot be renumbered by a reordering. ## Reading it back Because the values are real, you can compute them mentally by walking the declaration top to bottom, and you can verify them at runtime with a `console.log`. That is the whole trick to the interview question: state the two rules (first bare member is 0; each subsequent bare member is previous plus one), note that explicit initializers reset the counter, and explain that string members have no derivable successor so the compiler demands one for each.
- Someone inserts a new member into the middle of a numeric enum whose values are already stored in a database. What breaks?Every member after the insertion point shifts up by one, so stored rows silently start meaning the neighbouring member. Nothing fails to compile and no test necessarily catches it, because the old numbers are still valid enum values. The fix is to pin every value explicitly, or to use a string enum whose values cannot be renumbered by a reordering.
- Can a single TypeScript enum mix numeric and string members?Yes — TypeScript calls these heterogeneous enums and they compile. They are discouraged, because consumers can no longer assume a member's value has one type, and the emitted object ends up with reverse-map entries for only the numeric half. The usual rule still applies: a bare member following a string member is an error, since there is nothing to increment.
saying these in an interview costs you the question
- Says the first numeric member defaults to 1
- Claims string enum members auto-increment like numbers do
- Thinks every enum member must be initialized explicitly
- Assumes reordering numeric members is safe for stored values
- Believes an explicit initializer only affects that one member