skip to content

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 pageshow

explore

questions

page 2 of 2

In TypeScript, how do you type a rest parameter, and what changes when you type it as a tuple instead of an array?

level: middleimportance: should knowfreq 42%

basics

~20 s

A 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.

open as a page

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`?

level: middleimportance: should knowfreq 44%

basics

~10 s

The 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>.

open as a page

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?

level: middleimportance: should knowfreq 60%

basics

~20 s

Contextual 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.

open as a page

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?

level: middleimportance: should knowfreq 48%

basics

~20 s

In 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.

open as a page

In TypeScript, what values are assignable to `object`, to `{}` and to `Object`, and which of the three actually means "any non-primitive value"?

level: middleimportance: should knowfreq 45%

basics

~20 s

Only 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.

open as a page

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?

level: middleimportance: should knowfreq 47%

basics

~20 s

They 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.

open as a page

In TypeScript, what does a parameter annotated `flag: true` accept, and how does the type boolean relate to the literal types true and false?

level: middleimportance: should knowfreq 45%

basics

~20 s

A 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.

open as a page

In TypeScript, what is an "implicit any", and in which code positions does the compiler fall back to it?

level: middleimportance: should knowfreq 54%

basics

~20 s

An 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.

open as a page

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`?

level: middleimportance: should knowfreq 40%

basics

~20 s

string | 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.

open as a page

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?

level: middleimportance: should knowfreq 52%

basics

~20 s

TypeScript 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.

open as a page

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?

level: seniorimportance: should knowfreq 42%

basics

~20 s

It 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.

open as a page

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?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A 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.

open as a page

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?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Inlining 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.

open as a page

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?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Declare 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.

open as a page

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?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Mostly 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.

open as a page

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?

level: seniorimportance: should knowfreq 40%

basics

~20 s

No. 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.

open as a page

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?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Type 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.

open as a page

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?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Calling 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.

open as a page

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?

level: seniorimportance: should knowfreq 44%

basics

~20 s

An 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.

open as a page

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?

level: principalimportance: should knowfreq 30%

basics

~20 s

Decide 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.

open as a page

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?

level: principalimportance: should knowfreq 30%

basics

~20 s

Prefer 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".

open as a page

In TypeScript, what do the labels in the tuple type `[first: string, second?: number]` change, and what rule does the compiler enforce about labelling?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Labels 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.

open as a page

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?

level: middleimportance: nice to knowfreq 36%

basics

~20 s

A 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.

open as a page

In TypeScript, what is the difference between the `symbol` type and a `unique symbol` type, and where are you allowed to use `unique symbol`?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

The 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.

open as a page

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?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

A 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.

open as a page

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?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

An 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.

open as a page

showing 31–56 of 56