Types
Basic and primitive types, tuples, the any/unknown/never special types, and enums.
15 questions
JuniorTheoryVery commonHow does a union A | B differ from an intersection A & B?
How does a union A | B differ from an intersection A & B?
A | B is one of the two, so only the members both share are usable until you narrow it. A & B satisfies both at once, so the members of both are available. Widening the value set narrows usable members; constraints add them.
Common mistakes
- ✗Inverting the two — expecting a union to expose the members of both types
- ✗Forgetting that a union's members stay unusable until it is narrowed
- ✗Believing
string & numberis a valid pair of constraints rather thannever
Follow-up questions
- →What does
string & numberresolve to, and why? - →How do you reach a member that exists on only one arm of a union?
MiddleTheoryVery commonHow do any, unknown, and never differ in TypeScript?
How do any, unknown, and never differ in TypeScript?
any opts out of type checking — it is assignable to and from anything, so it gives no safety and is best avoided. unknown is the type-safe counterpart: any value is assignable to it, but you must narrow it with a type guard before use. never is the empty type with no values; it is the return type of a function that always throws or never returns, and the type of an exhausted union.
Common mistakes
- ✗Reaching for
anywhenunknownwould keep the value type-safe - ✗Forgetting that
unknownmust be narrowed before any member access - ✗Thinking
neverholds some value rather than being the empty type
Follow-up questions
- →Why does the compiler reject calling a method on an
unknownvalue? - →Where does
nevershow up in exhaustiveness checking?
MiddleTheoryVery commonHow do never, void, and undefined differ in TypeScript?
How do never, void, and undefined differ in TypeScript?
undefined is a real type with one value, undefined. void is the return type of a function that completes without returning a useful value — the caller must ignore the result. never is the empty type with no values at all; it types a function that never finishes normally (one that always throws or loops forever) and marks exhausted branches. So void returns nothing useful, while never never returns.
Common mistakes
- ✗Treating
voidandneveras the same —voidreturns,nevernever returns - ✗Thinking
undefinedis only a value and not also a type you can annotate - ✗Annotating a throwing or infinite-loop function as
voidinstead ofnever
Follow-up questions
- →Why is
neverrather thanvoidinferred for a function that always throws? - →How does
neverenable an exhaustiveness check over a union?
JuniorTheoryCommonWhat is a literal type, and how does 'a' | 'b' differ from string?
What is a literal type, and how does 'a' | 'b' differ from string?
A literal type's only inhabitant is one exact value — 'a', 42, or true. 'a' | 'b' admits exactly those two strings, while string admits every string. So the compiler rejects a typo like 'c' right at the call site.
Common mistakes
- ✗Assuming a literal union still accepts any value of the base type
- ✗Thinking the check happens at run time rather than during compilation
- ✗Not realising that numbers and booleans have literal types too
Follow-up questions
- →Why does
let mode = 'a'not get the literal type'a'? - →How does a literal union make a
switchexhaustively checkable?
JuniorTheoryCommonWhat is the unknown type, and why can't you use its value directly?
What is the unknown type, and why can't you use its value directly?
unknown is the type-safe top type: every value is assignable to it, but it is assignable to nothing else. You cannot call, index, or read a property from it until narrowed — typeof, instanceof, a type guard. any permits it all unchecked.
Common mistakes
- ✗Confusing
unknownwithnever—unknownaccepts every value,neveraccepts none - ✗Reaching for
asinstead of narrowing anunknownwithtypeofor a type guard - ✗Assuming
unknownpermits property access the wayanydoes
Follow-up questions
- →How do you narrow an
unknownreturned byJSON.parseinto a known shape? - →Why is
unknownassignable to no type other than itself andany?
MiddleTheoryCommonWhat is an enum in TypeScript and when do you use it?
What is an enum in TypeScript and when do you use it?
An enum defines a named set of related constants. Numeric enums auto-assign 0, 1, 2… from the first member, while string enums take explicit string values. Use one to replace magic numbers or strings with readable names and to group related options like HTTP methods or statuses. Numeric enums emit a runtime object with reverse mapping (value → name); string enums read clearly in output but have no reverse map.
Common mistakes
- ✗Assuming numeric enums start at
1— the first member is0by default - ✗Expecting string enums to provide a reverse value-to-name map
- ✗Forgetting that numeric enums emit a real runtime object, not just types
Follow-up questions
- →Why do numeric enums produce a reverse mapping but string enums do not?
- →When would a union of string literals be a better fit than an enum?
MiddleTheoryCommonHow does a tuple [string, number] differ from an array (string | number)[]?
How does a tuple [string, number] differ from an array (string | number)[]?
A tuple fixes the length and each position's type: t[0] is string, t[1] number. (string | number)[] fixes no length and types every index as the union, so reads need narrowing. The guarantee is compile-time only: push still runs.
Common mistakes
- ✗Expecting a tuple to enforce its length at run time —
pushstill compiles and runs - ✗Typing a fixed pair as
(string | number)[]and then narrowing at every read - ✗Thinking a tuple's indices are typed as the union of its element types
Follow-up questions
- →What does a rest element such as
[string, ...number[]]allow? - →Why does
as conston an array literal produce a readonly tuple?
MiddleTheoryCommonWhat does typeof do in a type position, and how does it differ from the JS operator?
What does typeof do in a type position, and how does it differ from the JS operator?
In a type position typeof x is a type query: it yields the static type inferred for x. It is erased and never runs, unlike the JavaScript operator returning a runtime string. It bridges a value into the type world: type Cfg = typeof config.
Common mistakes
- ✗Expecting
typeof xin a type position to yield the runtime string'object' - ✗Believing a type query emits code — it is erased entirely
- ✗Not using
typeofto derive a type from an existing config object or constant
Follow-up questions
- →How do
typeofandkeyofcombine to type an object's keys? - →Why is
typeofover anas constobject more useful than over a plain one?
MiddleTheoryCommonWhy is unknown the safer choice than any for a value of unpredictable shape?
Why is unknown the safer choice than any for a value of unpredictable shape?
any disables checking and spreads into every derived value, so mistakes surface at run time. unknown takes the same values but is assignable to nothing, forcing a typeof check or type guard first — errors surface while type-checking.
Common mistakes
- ✗Thinking
unknownadds a runtime check — the whole difference is at compile time - ✗Assigning an
unknownstraight into a typed variable without narrowing it first - ✗Not noticing that
anypropagates into every value derived from it
Follow-up questions
- →What breaks when an
anyfrom an untyped library leaks through a whole call chain? - →How do you turn an
unknownfromJSON.parseinto a checked domain type?
JuniorTheoryOccasionalWhat are the basic types available in TypeScript?
What are the basic types available in TypeScript?
The primitives are boolean, number (one numeric type for ints and floats), string, plus bigint and symbol. Collections come as arrays (number[] or Array<number>) and tuples — fixed-length arrays with a type per position ([string, number]). TypeScript also adds enum and the special types any, unknown, void, never, and null/undefined.
Common mistakes
- ✗Expecting separate
intandfloattypes —numbercovers both - ✗Confusing a tuple with an array — a tuple has fixed length and a type per position
- ✗Treating
any,unknown, andneveras values rather than types
Follow-up questions
- →How does a tuple type differ from a plain array type?
- →When would you reach for
bigintinstead ofnumber?
MiddleTheoryOccasionalHow do object, Object, and {} differ as types?
How do object, Object, and {} differ as types?
object is any non-primitive value — never a string or a number. Object is the Object.prototype interface, satisfied by almost every value, primitives included, so it barely constrains. {} is anything but null and undefined.
Common mistakes
- ✗Using
Objectas a constraint and being surprised that42satisfies it - ✗Reading
{}as an empty object rather than as anything butnullandundefined - ✗Assuming lowercase
objectand uppercaseObjectare synonyms
Follow-up questions
- →Why does
const x: {} = 42compile? - →What would you use instead of
objectto require a plain record shape?
MiddleTheoryOccasionalHow does a string enum differ from a numeric enum in TypeScript?
How does a string enum differ from a numeric enum in TypeScript?
A numeric enum auto-numbers from 0 and emits a reverse map, so E[0] yields 'A'. A string enum needs an explicit value per member, emits no reverse map, and is self-describing at run time. Both emit a real object, unlike a literal union.
Common mistakes
- ✗Expecting a reverse map on a string enum — only numeric enums emit one
- ✗Thinking enums are erased like a type alias — they emit a real runtime object
- ✗Relying on auto-increment in a string enum, where every member needs a value
Follow-up questions
- →Why does a string enum serialize more readably into JSON than a numeric one?
- →When is a plain literal union a better fit than an enum?
SeniorTheoryOccasionalHow does a const enum differ from a regular enum?
How does a const enum differ from a regular enum?
A regular enum compiles to a runtime object — extra JavaScript that supports reverse mapping (value → name) and can be iterated. A const enum is fully inlined at compile time: each member usage is replaced by its literal value and no object is emitted, which shrinks the output. The trade-off is that you lose reverse mapping and any runtime presence, and const enum clashes with isolatedModules and some bundlers, so it cannot be used where a runtime object is needed.
Common mistakes
- ✗Thinking
const enumstill emits a runtime object — it inlines instead - ✗Expecting reverse mapping from a
const enum, which it does not provide - ✗Using
const enumunderisolatedModulesor incompatible bundlers
Follow-up questions
- →Why does
const enumbreak underisolatedModules? - →When would the lost reverse mapping rule out a
const enum?
SeniorTheoryOccasionalWhy does the intersection string & number resolve to never?
Why does the intersection string & number resolve to never?
An intersection admits only values meeting both constraints; nothing is both string and number. That empty set is never, and the compiler reduces it while type-checking. Object intersections stay inhabitable unless a property clashes.
Common mistakes
- ✗Reading
neveras an error type rather than as the empty set of values - ✗Assuming object intersections collapse to
neverthe way primitive ones do - ✗Thinking the reduction happens at run time rather than during type checking
Follow-up questions
- →What makes an object intersection collapse to
never? - →Why does
neverdisappear from a union such asstring | never?
SeniorCodeOccasionalType a spread that prepends to a tuple and one that merges two objects
Type a spread that prepends to a tuple and one that merges two objects
A rest element preserves positions: prepend typed [H, ...T] keeps every index, while a plain array gives only T[]. Object spread overwrites, as at run time: a key present in both takes the second object's type. No any, no assertion.
Common mistakes
- ✗Annotating the prepend result as an array of the union, throwing away every position
- ✗Expecting an overlapping key to take its type from the first object rather than the second
- ✗Reaching for a type assertion to recover the tuple instead of a rest element in the return type
Follow-up questions
- →What does spreading a plain array rather than a tuple lose?
- →Why does a spread of two generic parameters produce
A & Brather than an overwritten shape?