This guide contains exactly 15 questions and reflects TypeScript 6.0 in September 2026. For a simple closed object shape, type and interface are often interchangeable. The useful interview answer focuses on openness, expressiveness, composition conflicts, augmentation, and measured compiler cost—not team folklore.
Table of Contents
- Type vs Interface Fundamentals Questions
- Declaration Merging Questions
- Unions and Advanced Types Questions
- Inheritance and Extension Questions
- Practical Usage Questions
- Quick Reference
Type vs Interface Fundamentals Questions
These questions test your understanding of the core differences between type aliases and interfaces.
What is the difference between type and interface in TypeScript?
Both type and interface can describe object shapes. Interfaces are open to compatible declaration merging and can compose object shapes with extends. Type aliases name any type expression, including unions, tuples, primitives, mapped types, conditional types, and intersections.
Think of interface as a contract or specification sheet—anything that implements this contract must have these properties and methods. A type alias is more like giving a name to any type expression—you're not creating a new type, you're creating a convenient alias for an existing type or combination of types.
// Interface: A contract for objects
interface User {
id: number;
name: string;
email: string;
}
// Type alias: A name for a type expression
type UserType = {
id: number;
name: string;
email: string;
};
// Both work identically for basic object typing
const user1: User = { id: 1, name: "Alice", email: "alice@example.com" };
const user2: UserType = { id: 2, name: "Bob", email: "bob@example.com" };For simple object definitions, they behave the same way. The differences emerge when doing more complex operations.
When should you use type vs interface in TypeScript?
The choice depends on the semantics you need. Do not equate “public” with “interface”: some public APIs should be deliberately closed, while others are designed for augmentation.
Use interface when:
- Designing an object contract that is deliberately open to augmentation
- Creating class contracts with
implements - You need declaration merging for library augmentation
- You want
extendsto reject incompatible property composition at the declaration
Use type when:
- Creating unions of literals or other types
- Defining tuple types
- Using mapped types, conditional types, or template literal types
- Creating utility types
- The type represents something other than an object shape
For simple object shapes with no special requirements, follow the existing codebase convention. Consistency matters more than the choice itself.
Can classes implement both interfaces and types?
Classes can implement interfaces and type aliases that resolve to object types with statically known members. A class cannot implement an arbitrary union because the required member set is not fixed. implements checks the instance side only and does not change or infer the class's own property types.
interface IUser {
id: number;
getName(): string;
}
type TUser = {
id: number;
getName(): string;
};
class InterfaceUser implements IUser {
id: number = 1;
getName() { return "Interface User"; }
}
class TypeUser implements TUser {
id: number = 2;
getName() { return "Type User"; }
}Only the interface can later participate in declaration merging. Whether that openness is a feature or a risk depends on the API.
Declaration Merging Questions
These questions test your understanding of one of the key differentiators between interfaces and types.
What is declaration merging in TypeScript?
Multiple compatible interface declarations with the same name merge. Type aliases cannot be reopened. Non-function members with the same name must have the same type; function members form overloads, with later groups generally ordered before earlier groups and specialized signatures receiving special treatment.
// First declaration
interface Config {
apiUrl: string;
timeout: number;
}
// Second declaration - TypeScript merges these
interface Config {
debugMode: boolean;
retryCount: number;
}
// The merged interface has all four properties
const config: Config = {
apiUrl: "https://api.example.com",
timeout: 5000,
debugMode: true,
retryCount: 3
};TypeScript combines these compatible declarations. Accidental merging can also surprise a codebase, so use distinctive names/modules and avoid relying on ambient global scope without intent.
If you try to declare a type with the same name twice, TypeScript gives an error:
type Config = { apiUrl: string };
type Config = { timeout: number }; // Error: Duplicate identifier 'Config'How do you extend third-party library types using declaration merging?
Module or global augmentation can extend an existing declaration when the runtime really has the corresponding member. The declaration does not patch JavaScript by itself.
export {}; // Make this file an external module.
declare global {
interface Window {
analytics: {
track: (event: string, properties?: object) => void;
};
}
}
// Now TypeScript knows about window.analytics
window.analytics.track("page_view", { page: "/home" });This is commonly used to add custom properties to global objects, extend Express request/response objects, or augment library configuration interfaces.
// Extending a library's interface
declare module "some-library" {
interface Config {
companyId: string;
environment: "dev" | "staging" | "prod";
}
}The module specifier must resolve to the same module as the original declaration. Augmentation cannot add new top-level declarations or augment a default export by its default name. It is not the only integration option: a wrapper/adapter, a local derived type, or an upstream type fix is often safer when the runtime object should not be patched.
Unions and Advanced Types Questions
These questions test your understanding of type-only features that interfaces cannot replicate.
Why can't interfaces represent union types?
An interface declaration creates a single open object-like contract (including property, call, construct, or index signatures). It cannot alias an arbitrary union expression. A type alias can name that expression.
// Union types - only possible with type
type Status = "pending" | "approved" | "rejected";
type NumericId = number | bigint;
type Nullable<T> = T | null | undefined;
// You cannot create these with interfaces
interface Status = "pending" | "approved" | "rejected"; // Syntax error!Status is a union of string literals and is erased with other types. It is useful when no runtime enum object is required.
How do you create mapped types and conditional types?
Types excel at creating utility types and working with mapped types—operations that transform one type into another based on rules.
// Mapped type - creating a read-only version of any type
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
// Conditional type - extracting return type of a function
type MyReturnType<T> =
T extends (...args: never[]) => infer Result ? Result : never;
// Template literal types
type EventName = `on${Capitalize<string>}`;
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiEndpoint = `/${string}`;
// Combining them
type ApiRoute = `${HttpMethod} ${ApiEndpoint}`;
// Valid: "GET /users", "POST /orders"These teaching aliases avoid shadowing the built-in Readonly and ReturnType utilities. Mapped and conditional transformations require type aliases; an interface may still be used as their input or output's structural constituent.
How do you create discriminated union types for API responses?
Discriminated unions combine union types with a common "discriminator" property that TypeScript uses for type narrowing. This pattern is only possible with types.
// Use types for discriminated unions
type ApiResponse<T> =
| { status: "success"; data: T; timestamp: string }
| { status: "error"; error: { code: number; message: string }; timestamp: string }
| { status: "loading" };
// Interface for the shape of specific response data
interface UserData {
id: string;
name: string;
email: string;
}
// Usage with type narrowing
function handleResponse<T>(response: ApiResponse<T>): T | null {
switch (response.status) {
case "success":
// TypeScript knows response.data exists and is type T
console.log(`Success at ${response.timestamp}`);
return response.data;
case "error":
// TypeScript knows response.error exists
console.error(`Error ${response.error.code}: ${response.error.message}`);
return null;
case "loading":
// TypeScript knows this is the loading state
console.log("Still loading...");
return null;
}
}This pattern uses a type for ApiResponse because it requires a discriminated union, combined with interfaces for specific data shapes.
Inheritance and Extension Questions
These questions test your understanding of how interfaces and types handle composition and inheritance.
How do interfaces inherit using the extends keyword?
Interfaces use extends to compose one or more compatible object types. Because TypeScript is structural, this is type composition rather than runtime class inheritance.
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
// Multiple interface extension
interface Pet {
owner: string;
}
interface HouseDog extends Dog, Pet {
isHouseTrained: boolean;
}
const myDog: HouseDog = {
name: "Max",
age: 3,
breed: "Labrador",
bark() { console.log("Woof!"); },
owner: "Alice",
isHouseTrained: true
};Dog includes the members of Animal and adds its own. HouseDog composes Dog and Pet. If two bases declare an incompatible property, TypeScript reports the conflict at the interface declaration.
How do types compose using intersection?
Type aliases can combine types with an intersection (&), but an intersection is not always equivalent to interface extension.
type Animal = {
name: string;
age: number;
};
type Dog = Animal & {
breed: string;
bark(): void;
};
type Pet = {
owner: string;
};
type HouseDog = Dog & Pet & {
isHouseTrained: boolean;
};For compatible object properties, the result often looks alike. For incompatible properties, extends reports an error immediately, while an intersection can retain an impossible property such as string & number, which reduces to never:
interface Named { id: string }
interface Numbered { id: number }
// Error at declaration: incompatible id properties.
// interface Invalid extends Named, Numbered {}
type Impossible = Named & Numbered;
// Impossible['id'] is never.Can interfaces extend types and vice versa?
Interfaces and type aliases are interoperable. An interface can extend an alias that resolves to a statically known object type or compatible intersection; it cannot extend an arbitrary union. A type alias can intersect an interface or include it in a union.
// Interface extending a type
type BaseUser = {
id: number;
email: string;
};
interface AdminUser extends BaseUser {
adminLevel: number;
permissions: string[];
}
// Type intersecting with an interface
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
type AuditableUser = AdminUser & Timestamps;Understanding that interfaces and types are interoperable, not competing features, demonstrates deeper TypeScript knowledge.
Practical Usage Questions
These questions test your ability to apply type vs interface knowledge in real scenarios.
Which is more performant: type or interface?
A simple object interface and a simple object type alias do not justify a universal performance ranking. Official compiler guidance specifically prefers interface extends over large intersections: interfaces create a flat object type, surface conflicts, display more consistently, and allow relationships to be cached; intersections can require recursive constituent checks and produce never.
Choose the correct semantics first. If type checking is slow, use tsc --extendedDiagnostics or a compiler trace to find the expensive relation, then simplify the measured union/intersection/conditional or add a helpful named annotation. Do not churn a codebase from type to interface based on folklore.
How do you create a type-safe event system?
This problem demonstrates practical understanding of when to use types vs interfaces together.
// Either an interface or object type alias can define the event map.
interface EventMap {
userCreated: { userId: string; email: string };
orderPlaced: { orderId: string; amount: number };
paymentProcessed: { transactionId: string; status: "success" | "failed" };
}
// Interface for the emitter contract
interface TypedEventEmitter<Events extends object> {
on<K extends keyof Events>(
event: K,
handler: (payload: Events[K]) => void
): () => void;
emit<K extends keyof Events>(event: K, payload: Events[K]): void;
}
// Usage - fully type-safe!
const emitter: TypedEventEmitter<EventMap> = /* implementation */;
emitter.on("userCreated", (payload) => {
// TypeScript knows payload has userId and email
console.log(`User ${payload.userId} created with email ${payload.email}`);
});
emitter.emit("orderPlaced", { orderId: "123", amount: 99.99 }); // ✓ Type-safe
emitter.emit("orderPlaced", { orderId: "123" }); // ✗ Error: missing 'amount'keyof works with both forms; EventMap could be a type alias or interface. The emitter is an interface because deliberate implementability/openness is useful here, not because keyof requires either keyword. Its implementation must preserve the event-to-payload correlation internally, and external payloads still require runtime validation.
How do you create a utility type that makes some properties required?
This requires mapped types and conditional logic, making type the only choice.
type RequireOnly<T, K extends keyof T> =
Omit<Partial<T>, K> & Required<Pick<T, K>>;
interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
type CreateUserInput = RequireOnly<User, "name" | "email">;
// Result: { id?: number; name: string; email: string; avatar?: string }The RequireOnly utility type makes all properties optional except for the specified keys. This is a common pattern for form inputs where some fields are required and others are optional.
How do you handle team disagreements on type vs interface conventions?
Document semantic exceptions before choosing a default: unions/mapped/conditional types require aliases; intended augmentation favors interfaces; conflict-sensitive composition may favor extends. Then use code review and, if the team wants a mechanical default, the current @typescript-eslint/consistent-type-definitions rule in the project's active flat-config setup. Consistency should not override correctness or library augmentation requirements.
Quick Reference
| Feature | Interface | Type |
|---|---|---|
| Object shapes | Yes | Yes |
| Declaration merging | Yes | No |
| Extends/inheritance | extends keyword | Intersection & |
| Implements (classes) | Yes | Yes |
| Union types | No | Yes |
| Tuple types | No | Yes |
| Primitive aliases | No | Yes |
| Mapped type declaration | No | Yes |
| Conditional types | No | Yes |
| Computed property signatures | Supported for permitted literal/unique-symbol names | Supported in object aliases; mapped types require alias |
| Composition conflicts | extends reports them at declaration | & may reduce a member to never |
| Performance | Prefer extends over large intersections when composing | Simple object aliases are fine; measure complex expressions |
| Error display | Named, flat object shape | Simple aliases are clear; nested intersections can be complex |
Key takeaways:
- Both are valid for object shapes; consistency matters more than the choice
- Interfaces excel when declaration merging, augmentation, or
extendssemantics are intentional - Types excel at unions, computed types, and advanced type operations
- They're complementary tools, not competitors
- Follow your team's conventions; advocate for clear guidelines if none exist
Frequently Asked Questions
What is the difference between type and interface in TypeScript?
Both can name object shapes in TypeScript's structural type system. An interface is open to compatible declaration merging and supports extends; a type alias names any type expression, including primitives, unions, tuples, mapped types, conditional types, and intersections. The important composition difference is conflict handling: interface extension reports incompatible properties at the declaration, while an intersection may combine them into an unusable never property.
When should I use type vs interface in TypeScript?
Use a type alias when the type is a union, tuple, primitive alias, conditional or mapped transformation, or another expression an interface cannot name. Use an interface when deliberate openness, declaration or module augmentation, implements, or extends-based object composition improves the API. For a closed simple object, either is valid; follow the codebase convention and do not promise future extensibility without a reason.
Can interfaces extend types in TypeScript?
An interface can extend a type alias only when the target resolves to a statically known object type or intersection of object types with statically known members; it cannot extend an arbitrary union. A type alias can compose an interface with an intersection. These forms are similar for compatible properties, but extends rejects conflicts early while intersections can produce never.
What is declaration merging in TypeScript interfaces?
Compatible interface declarations with the same name merge. Non-function members with the same name must have the same type; function members form overloads whose ordering has rules. Type aliases cannot be reopened. Merging enables module and global augmentation, but accidental name collisions or incompatible upstream changes are risks, so augmentation should be narrow, tested, and paired with the runtime implementation.
Which is faster: type or interface in TypeScript?
A simple interface and a simple object type alias are not usefully ranked by a universal speed claim. Official performance guidance prefers interface extends over large intersections because interfaces flatten conflicts and relationships can be cached, while intersections may be recursively combined and yield never. Choose the correct model first; if type checking is slow, measure with extended diagnostics or a trace and optimize the measured hotspot.
Can I use both type and interface together in TypeScript?
Yes. A type alias can intersect or form a union with an interface, and an interface can extend a compatible object type alias. Classes can implement interfaces and object-like aliases, but not unions with unknown member sets. Combining both is normal: choose each construct for its semantics rather than forcing every declaration into one style.
Sources
- TypeScript 6.0 announcement
- TypeScript Handbook: Object Types
- TypeScript Handbook: Everyday Types and type aliases
- TypeScript Handbook: Declaration Merging and Module Augmentation
- TypeScript Handbook: Modules Reference
- TypeScript Handbook: Classes and implements
- TypeScript Wiki: Performance
- typescript-eslint: consistent-type-definitions
Related Articles
- Complete Frontend Developer Interview Guide - comprehensive preparation guide for frontend interviews
- TypeScript Generics Interview Guide - Master generic types, constraints, and utility types
- 16 Tricky TypeScript Interview Questions - advanced type-system edge cases
- React 19 Interview Guide - Actions, Effect Events, Activity, and React Compiler
