skip to content

Interfaces & Type Aliases

The constructs you use to describe the shape of an object in TypeScript — interfaces, type aliases, and the structural rules that decide when one shape is assignable to another. Interviewers open almost every TypeScript screen here because 'interface or type?' quickly exposes whether you understand declaration merging, structural typing, and excess property checks or just memorized syntax.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

page 2 of 2

In TypeScript, what does the `readonly` modifier on an object-type property such as `readonly db: { url: string }` actually prevent, and what does it leave untouched?

level: middleimportance: should knowfreq 55%

basics

~20 s

readonly stops the checker from accepting an assignment to that property through that type. It is shallow, so the object the property points at stays fully mutable, and it is erased at compile time, so nothing is protected at runtime.

open as a page

A TypeScript service has `function emit(e: { id: string }) { queue.push(JSON.stringify(e)) }`. In production the queued payloads contain fields nobody declared, including sensitive ones. Explain why the type system permitted this and how you would prevent it.

level: seniorimportance: should knowfreq 36%

basics

~20 s

An object type is a lower bound: it requires at least the declared members and never forbids others. Callers legitimately pass wider objects, and since types are erased, the extra fields are still there when JSON.stringify walks the value at runtime.

open as a page

In TypeScript, a branded type such as `type Email = string & { readonly __brand: 'Email' }` can only be produced with an `as` assertion. How do you structure code so that assertion is trustworthy, and what does the brand still not guarantee?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Confine the assertion to one exported factory that checks the value first and returns the branded type or a failure, and export the type without any other way to mint it. The brand still guarantees nothing at runtime: it records that a value passed through that factory, nothing about the value itself.

open as a page

A TypeScript codebase annotates every callback parameter with the built-in `Function` type. What checking does that give up, and what should replace it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Function says only that a value is callable: any argument list type-checks and every call produces any. Replace it with an explicit call signature giving parameter and return types, or with a constraint such as (...args: never[]) => unknown where the function is never actually called.

open as a page

Given `interface Logger { (message: string): void; level: number }` in TypeScript, why does `const logger: Logger = (message) => {}` fail to compile, and how do you build a conforming value without an `as` assertion?

level: seniorimportance: should knowfreq 32%

basics

~20 s

An arrow function satisfies the call signature but has no level property, so the initializer is missing a member. Build the value with Object.assign, whose result type is the callable intersected with the properties, or assign properties onto a function declaration so the compiler folds them into its type.

open as a page

You ship a TypeScript library whose published types include `interface RequestOptions`. What can consumers do with that declaration that they could not if you had exported it as a type alias, and why might you not want them to?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Consumers can add members to an exported interface from their own code by redeclaring it in a module augmentation; an exported type alias is closed and a second declaration is an error. That extensibility is a public commitment you cannot easily withdraw.

open as a page

In TypeScript, you want every class implementing your `Codec` interface to also expose a static `fromJSON` method. Why does putting it in the interface and writing `class Doc implements Codec` fail to enforce that, and what does enforce it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

An implements clause checks only the instance side, so static members and construct signatures written in the interface are never enforced on the class. Declare a separate static-side interface and check the class value against it, with an annotated const or a satisfies expression.

open as a page

In TypeScript, when two same-name interface declarations each declare a member called `format`, when is that a compile error and when do you get an overload set?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Method-syntax members of the same name become an overload set when interfaces merge. Ordinary properties, including function-typed ones, must be unique or declared with an identical type — otherwise the compiler reports that subsequent property declarations must have the same type.

open as a page

In TypeScript, a teammate silences an "Object literal may only specify known properties" error by writing `as Config` on the literal. What does that assertion give up, and what should they use instead — including where `satisfies Config` does and does not help?

level: seniorimportance: should knowfreq 46%

basics

~20 s

as removes the literal's freshness, so it silences every excess-property complaint in that literal, not just the intended one — future typos included. The honest fixes change the type: declare the extra property, or widen the contract. satisfies checks without widening but still rejects undeclared keys.

open as a page

In TypeScript, does swapping the operands of an intersection type — writing `B & A` instead of `A & B` — ever change how the resulting type behaves?

level: seniorimportance: should knowfreq 30%

basics

~20 s

For property members, no: each member's type is the intersection of both contributions, and that is commutative, so the two orderings are mutually assignable. Order does matter for call and construct signatures, which are concatenated in operand order and resolved first-match-wins.

open as a page

In TypeScript a function is overloaded as `(x: string): string` and `(x: number): number`. A caller holds a value typed `string | number` and passes it — why is that a compile error, and what are the options?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Overload resolution commits to one signature, and neither accepts a union, so a string | number argument matches nothing and the compiler reports no matching overload. Fix it by narrowing at the call site, or drop overloads for a single union-parameter signature.

open as a page

What does enabling TypeScript's `exactOptionalPropertyTypes` compiler option change about optional properties, and how do you declare one that must still accept an explicit `undefined`?

level: seniorimportance: should knowfreq 36%

basics

~20 s

With exactOptionalPropertyTypes on, a property marked ? means only "the key may be missing" — writing the key with the value undefined becomes an error. To allow that, declare the type explicitly as name?: string | undefined.

open as a page

A TypeScript object whose property is declared `readonly` still gets overwritten at runtime, with no compile error anywhere in the codebase. What holes in the modifier allow that, and what would actually stop it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Assignability ignores readonly on object properties, so the value can be assigned to an alias typed without it and written through that alias. The modifier is also shallow and erased, so nested data, assertions, and untyped callers bypass it entirely.

open as a page

A service returns a bag of feature flags keyed by flag name. In TypeScript, argue the tradeoffs between typing it as an open dictionary with a string index signature and typing it as a closed object type with one property per known flag.

level: principalimportance: should knowfreq 30%

basics

~20 s

An open dictionary never goes stale but gives up autocomplete, typo detection and any presence guarantee; a closed object type restores all three but becomes a false claim the moment the service ships a flag it does not list. The usual answer is both, separated by a validating boundary.

open as a page

In TypeScript, why is an instance of `class Meters { constructor(private value: number) {} }` not assignable to `class Feet { constructor(private value: number) {} }`, even though the two classes have identical shapes?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Private and protected members are matched by declaration, not by name and type. Each class declares its own value, so the two never match and the classes compare nominally instead of structurally — the one place TypeScript is nominal by default.

open as a page

In TypeScript, what does declaring a `namespace` with the same name as an existing interface, class, or function give you, and what ordering rule applies?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

The namespace merges with the other declaration, so one name serves as both a type and a container of values — a companion for an interface, or static members hung on a class, function or enum. The namespace must come after the declaration it merges with.

open as a page

In TypeScript an interface may extend a class, as in `interface Renderable extends Widget { label: string }`. What does the interface inherit from the class, and what restricts which types can implement it?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

The interface inherits the class's instance-side members — property and method types, without any implementations, and without the constructor or statics. It also inherits private and protected members, and because those are nominal, only the class itself or a subclass can implement the interface.

open as a page

In TypeScript, why is `interface Lookup { [i: number]: string | number; [key: string]: string }` rejected, while swapping the two value types compiles?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

When a type declares both index signatures, the numeric signature's value type must be assignable to the string signature's, because a numeric key is reachable as a string key too. Here string | number does not fit string, so it is rejected.

open as a page

In TypeScript, what do you gain by branding with a module-private `declare const brand: unique symbol` used as the key, compared with a plain property key such as `__brand: 'UserId'`?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

A unique symbol key cannot be written by code that does not have the symbol, so brands cannot be forged or matched by accident, and it cannot collide with a real data property. Give each brand its own symbol and several brands can compose on one value.

open as a page

Your organisation wants a single house rule for `interface` versus `type` across a large TypeScript codebase. What rule would you set, where must it allow exceptions, and how much is the consistency actually worth?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Set a default rather than a mandate: interface for declared object shapes, type alias for everything else, with language-forced exceptions for unions, tuples, primitives and derived types. Apply it to new code only, and reserve real review attention for published, extensible surfaces.

open as a page

Your team proposes augmenting a third-party library's exported interface in TypeScript so a field it does not declare becomes visible everywhere. What do you weigh before accepting that over defining a local type?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Augmentation is program-wide, unconditional and unenforced: every file sees the field whether or not it imports yours, two packages adding the same name collide hard, and the type promises something no runtime code checks. Prefer a local type unless third-party code must see the change.

open as a page

showing 31–51 of 51