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 pageshowhide
explore
- Interface vs Type Alias14 questions
- Declaration Merging and Augmentation6 questions
- extends vs Intersection (&)4 questions
- Choosing Between Interface and Type Alias4 questions
- Object Shape Features14 questions
- Optional and readonly Modifiers5 questions
- Index Signatures and Dictionary Types5 questions
- Excess Property and Weak Type Checks4 questions
- Callable and Constructable Types13 questions
- Call Signatures and Hybrid Types5 questions
- Construct Signatures and the Static Side4 questions
- Overload Signatures4 questions
- Structural Typing10 questions
- Assignability and Duck Typing5 questions
- Branded and Nominal-Emulation Patterns5 questions
questions
page 2 of 2In 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?
basics
~20 sreadonly 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.
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.
basics
~20 sAn 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.
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?
basics
~20 sConfine 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.
A TypeScript codebase annotates every callback parameter with the built-in `Function` type. What checking does that give up, and what should replace it?
basics
~20 sFunction 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.
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?
basics
~20 sAn 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.
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?
basics
~20 sConsumers 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.
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?
basics
~20 sAn 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.
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?
basics
~20 sMethod-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.
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?
basics
~20 sas 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.
In TypeScript, does swapping the operands of an intersection type — writing `B & A` instead of `A & B` — ever change how the resulting type behaves?
basics
~20 sFor 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.
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?
basics
~20 sOverload 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.
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`?
basics
~20 sWith 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.
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?
basics
~20 sAssignability 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.
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.
basics
~20 sAn 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.
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?
basics
~20 sPrivate 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.
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?
basics
~20 sThe 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.
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?
basics
~20 sThe 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.
In TypeScript, why is `interface Lookup { [i: number]: string | number; [key: string]: string }` rejected, while swapping the two value types compiles?
basics
~20 sWhen 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.
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'`?
basics
~20 sA 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.
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?
basics
~20 sSet 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.
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?
basics
~20 sAugmentation 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.
showing 31–51 of 51