Basic Types & Annotations
The type vocabulary you reach for in every TypeScript file: primitives, literals, arrays and tuples, the special types (any/unknown/never/void), enums and their modern alternatives, and how you annotate functions. Interviewers open here because sloppy answers about any vs unknown or enums reveal exactly how much of the type system a candidate actually reasons about.
part ofTypeScriptoverview, primer and where to startread it →on this pageshowhide
explore
- Primitive & Literal Types13 questions
- Primitive Type Annotations5 questions
- Literal Types4 questions
- const Assertions (as const)4 questions
- Arrays & Tuples10 questions
- Array Types & readonly Arrays5 questions
- Tuple Types5 questions
- Special Types13 questions
- any vs unknown4 questions
- never & Its Uses5 questions
- Enums & Alternatives10 questions
- Numeric, String & const Enums5 questions
- Function Annotations10 questions
- Parameter & Return Type Annotations5 questions
- Optional, Default & Rest Parameters5 questions
questions
page 2 of 2In TypeScript, how do you type a rest parameter, and what changes when you type it as a tuple instead of an array?
basics
~20 sA rest parameter must be typed as an array or a tuple, and must be the last parameter. An array type such as ...args: number[] accepts any number of arguments; a tuple type such as ...args: [x: number, y: number] pins the exact arity and names each position.
In TypeScript, what return type is inferred for `async function loadCount() { return 1; }`, and what happens if you annotate that function's return type as `number`?
basics
~10 sThe inferred return type is Promise<number>. Annotating it as number is a compile error: an async function's declared return type must be a promise type, so the correct annotation is Promise<number>.
In TypeScript, `['a', 'bb'].map(s => s.length)` compiles with no annotation on `s`, but `const len = (s) => s.length;` is an error under `noImplicitAny`. What is the difference between the two positions?
basics
~20 sContextual typing. The callback sits in a position whose expected type is already known — the declared signature of the map callback — so its parameter takes its type from there. The standalone arrow has no expected type, so nothing constrains its parameter.
A TypeScript codebase declares `type Logger = (string) => void;` and, oddly, values of that type accept an argument of any type at all. What does that type expression actually mean, and how should it be written?
basics
~20 sIn a function type expression every parameter must be named and annotated separately. (string) => void declares one parameter named string whose type is implicitly any — not a string parameter. The correct form is (message: string) => void.
In TypeScript, what values are assignable to `object`, to `{}` and to `Object`, and which of the three actually means "any non-primitive value"?
basics
~20 sOnly lowercase object means "non-primitive": it accepts arrays, functions and class instances and rejects every primitive. The type {} means "anything except null and undefined", so strings and numbers satisfy it, and Object behaves essentially the same way.
In TypeScript, what is the difference between writing `as const` on an object literal and writing `satisfies SomeType` after it, and when would you use both together?
basics
~20 sThey do different jobs. as const pins inference: literal types are kept and everything becomes readonly. satisfies checks the literal against a type without replacing the inferred type. Written together, as const satisfies T both pins the values and validates the shape.
In TypeScript, what does a parameter annotated `flag: true` accept, and how does the type boolean relate to the literal types true and false?
basics
~20 sA parameter typed true accepts only the value true, not any truthy value. The type boolean behaves as the union true | false, so true is assignable to boolean but a value typed boolean is not assignable to true.
In TypeScript, what is an "implicit any", and in which code positions does the compiler fall back to it?
basics
~20 sAn implicit any is the type the compiler falls back to when it can infer nothing — typically an unannotated parameter, an uninitialised member, or an untyped import. The noImplicitAny option turns those silent fallbacks into errors, and it is on under strict.
In TypeScript, what do `string | never` and `string & never` each reduce to, and why does `{ a: string } & { a: number }` give the `a` property the type `never`?
basics
~20 sstring | never reduces to string and string & never reduces to never. Because never is the empty set of values, it disappears from a union and swallows an intersection — and intersecting two incompatible property types leaves that property with no possible value.
In TypeScript, why does `[1, 2, 3].forEach(n => results.push(n))` type-check, even though Array.prototype.push returns a number and forEach's callback is declared to return void?
basics
~20 sTypeScript deliberately lets a function returning any type be assigned to a function type whose return type is void. The return value is simply ignored — at the call site the result is still typed void, so it cannot be used.
You change a function's parameter annotation in TypeScript from `items: Item[]` to `items: readonly Item[]`. What does that annotation actually guarantee, and what does it not?
basics
~20 sIt guarantees only that the function body cannot call mutating array methods or assign to indices, and only while type checking. It is shallow, so element objects stay mutable; it is erased at runtime; and the caller still holds a mutable reference to the same array.
Given `const point: [number, number] = [1, 2]` in TypeScript, why does `point.push(3)` compile even though the type says the tuple holds exactly two elements, and what annotation prevents it?
basics
~20 sA tuple is an ordinary array at runtime, so it inherits Array's methods, and push is typed to accept the tuple's element types. The length guarantee covers assignment and indexing, not mutation. Annotating the value as readonly [number, number] removes push and the other mutators.
A published TypeScript package declares `declare const enum Flag` in its .d.ts, and a consumer building with `isolatedModules` gets "Cannot access ambient const enums when 'isolatedModules' is enabled". Why does single-file transpilation make ambient const enums unusable, and what would you change?
basics
~20 sInlining a const enum member requires reading the declaration that defines it, which is a whole-program operation. isolatedModules promises every file can be transpiled alone, and an ambient declaration emits no runtime object to fall back on, so the compiler rejects it. Ship a plain enum instead.
You need a closed set of order states in TypeScript that is checked at compile time and can also be listed at runtime, without declaring an enum. How do you model it, and what do you give up?
basics
~20 sDeclare a frozen-shape object with as const for the runtime side, then derive the type from it with an indexed access over its own keys. You get the same member-style call sites and an iterable object, and you lose the enum's nominal behaviour.
In a TypeScript review you see every local variable annotated, as in `const count: number = items.length`. Is that worth flagging, and where does an explicit annotation on a variable genuinely earn its place?
basics
~20 sMostly yes: annotating what inference already knows adds noise and can silently launder an any, because any is assignable to every type. Annotations earn their place on declarations inference cannot resolve — empty collections, uninitialised or nullish-initialised variables, and exported values.
A shared configuration object in a TypeScript service is declared with `as const`. A teammate says this makes it immutable. Is that accurate, and how would you actually guarantee the object is not mutated at runtime?
basics
~20 sNo. A const assertion is a compile-time constraint that is erased at emit — it produces no runtime protection. Direct writes are rejected by the checker, but any alias typed as mutable, any cast, or any untyped consumer can still change the object. Runtime immutability needs a runtime mechanism.
Values entering a TypeScript service from JSON.parse, HTTP response bodies and untyped third-party modules all arrive typed as `any`. How do you keep that `any` from spreading into the rest of the codebase?
basics
~20 sType each boundary value as unknown at the point it enters, then validate it into a real type inside one wrapper per boundary, so no any crosses into application code. Assertions do not count — they check nothing at runtime.
In TypeScript, `declare const xs: string[] | number[];` and then `xs.push("a")` fails with "Argument of type 'string' is not assignable to parameter of type 'never'". Why does `never` appear in that message, and how would you fix it?
basics
~20 sCalling a method on a union of array types combines the candidate signatures, and the parameter types intersect: string & number, which is never. No argument satisfies both element types, so every call is rejected. Fix by widening the element type or narrowing the array first.
In TypeScript, `items.forEach(async item => { await save(item); })` compiles without complaint even though the callback is asynchronous. Why does the type checker accept it, and what goes wrong when it runs?
basics
~20 sAn async callback returns Promise<void>, and a callback type declared to return void accepts a function returning any type. Nothing consumes the promise, so forEach returns before the work finishes and any rejection becomes an unhandled rejection.
Your TypeScript monorepo models closed value sets inconsistently — some packages use enums, others string-literal unions. How would you decide on a standard, and what would you do about numeric enums whose values are already stored in the database and sent on the wire?
basics
~20 sDecide from constraints, not taste: does the value cross a serialization boundary, is it needed at runtime, and does the build require erasable syntax. Then change the type layer while freezing the stored values, and migrate on touch rather than all at once.
A TypeScript codebase uses null in some places and undefined in others to mean "no value". As the lead, how do you decide on a single convention, and what does the type system actually do differently with each?
basics
~20 sPrefer undefined internally, because optional properties and parameters already produce it, and convert null to undefined at the boundaries where JSON brings it in. Keep null only where you genuinely need a third state meaning "explicitly cleared".
In TypeScript, what do the labels in the tuple type `[first: string, second?: number]` change, and what rule does the compiler enforce about labelling?
basics
~20 sLabels are documentation for humans and tooling: they show in editor hints and become the parameter names when the tuple is used as a rest parameter list. They do not affect type identity or assignability. The rule is all-or-nothing — every element must be labelled, or none.
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?
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.
In TypeScript, what is the difference between the `symbol` type and a `unique symbol` type, and where are you allowed to use `unique symbol`?
basics
~20 sThe symbol type covers all symbols interchangeably; a unique symbol is a subtype tied to one specific declaration, so the checker can tell it apart from every other symbol. It is only allowed on const declarations and readonly static properties.
In TypeScript 5.x, why does the type `'red' | 'blue' | string` behave exactly like `string`, and what is the `(string & {})` idiom that library authors write to avoid it?
basics
~20 sA union absorbs members that are subtypes of another member, and every string literal type is a subtype of string, so the literals are reduced away and only string remains. Writing (string & {}) instead of string blocks that reduction and keeps editor suggestions.
In TypeScript, what does an optional property declared with type `never` — as in `{ value: string; defaultValue?: never }` — actually accomplish, and where does the pattern break down?
basics
~20 sAn optional never property forbids a key: it can only be absent or explicitly undefined. Paired across the branches of a union, it makes two properties mutually exclusive, so an object supplying both matches neither branch.
showing 31–56 of 56