Most application data is object-shaped: users, products, posts, settings, API responses, form values, and component props. TypeScript lets you describe those shapes clearly so property names and value types are checked before runtime.
Inline object types are useful for small examples or one-off function parameters. When an object shape is reused or important to the domain, give it a name with an interface or type alias.
const user: {
id: number;
name: string;
email?: string;
} = {
id: 1,
name: "Kavya",
};
function displayUser(profile: { id: number; name: string }): string {
return `#${profile.id} ${profile.name}`;
}
An interface gives a reusable name to an object contract. Interfaces are common for models, component props, service inputs, configuration objects, and class contracts.
Interfaces are especially readable when the shape represents a real business concept. A future developer can understand Product faster than a long inline object type repeated in several places.
interface Product {
readonly id: number;
name: string;
price: number;
tags?: string[];
}
function formatProduct(product: Product): string {
return `${product.name}: Rs. ${product.price}`;
}
const keyboard: Product = {
id: 1,
name: "Keyboard",
price: 1499,
};
An optional property may be missing. This is useful for values such as profile photos, descriptions, coupon codes, secondary contact details, and draft-only fields.
readonly prevents reassignment through that property. It is useful for IDs, timestamps, and data that should be treated as immutable after creation. It does not deeply freeze every nested object at runtime.
| Syntax | Meaning | Example |
|---|---|---|
email?: string | Property may be missing | User profile email |
readonly id: number | Cannot be reassigned through that property | Database ID |
items: string[] | Property is required | Cart items |
interface Profile {
readonly id: number;
name: string;
avatarUrl?: string;
}
function getAvatar(profile: Profile): string {
return profile.avatarUrl ?? "/images/default-avatar.png";
}
const profile: Profile = { id: 1, name: "Asha" };
console.log(getAvatar(profile));
Real data often contains nested objects. You can write nested shapes inline, but naming nested structures usually makes code easier to read, test, and reuse.
If a nested structure is reused or has its own rules, extract it into a separate interface. This keeps the parent model focused and avoids giant object definitions.
interface Address {
city: string;
country: string;
postalCode?: string;
}
interface UserProfile {
id: number;
name: string;
address: Address;
}
function formatAddress(profile: UserProfile): string {
return `${profile.address.city}, ${profile.address.country}`;
}
Interfaces can describe methods as well as data properties. This is useful for service contracts, repositories, validators, formatters, and objects that expose behavior.
A method signature describes the parameters and return type. Any object with a matching method can be used where the interface is expected.
interface UserRepository {
findById(id: number): Promise<UserProfile | null>;
save(profile: UserProfile): Promise<void>;
}
async function loadProfile(repo: UserRepository, id: number): Promise<string> {
const profile = await repo.findById(id);
return profile ? profile.name : "Unknown user";
}
Interfaces can extend other interfaces. This helps you build a larger shape from smaller concepts without repeating fields.
Use extension when the relationship is natural. If two shapes only happen to share a few fields, a separate type may be clearer than forcing inheritance-like structure.
interface BaseEntity {
readonly id: number;
createdAt: string;
}
interface BlogPost extends BaseEntity {
title: string;
slug: string;
published: boolean;
}
const post: BlogPost = {
id: 10,
createdAt: "2026-05-24",
title: "TypeScript Interfaces",
slug: "typescript-interfaces",
published: true,
};
TypeScript checks object literals carefully. If you pass an object literal with a property that the target type does not expect, TypeScript reports it. This catches spelling mistakes and wrong API fields early.
This check is strongest on fresh object literals. If a value is stored in a variable first, TypeScript checks structural compatibility rather than exact property lists.
interface Course {
title: string;
lessons: number;
}
const course: Course = {
title: "TypeScript",
lessons: 15,
// lessonCount: 15, // Error: unknown property
};
const rawCourse = {
title: "TypeScript",
lessons: 15,
lessonCount: 15,
};
const accepted: Course = rawCourse; // structurally compatible
Interfaces and type aliases can both describe object shapes. Interfaces are often preferred for object contracts that may be extended. Type aliases are useful for unions, tuples, primitive aliases, mapped types, and complex compositions.
Many teams use both. The practical rule is simple: use interfaces for object models and service contracts, and use type aliases when you need unions or type transformations.
| Need | Common Choice |
|---|---|
| Reusable object model | Interface |
| Component props object | Interface or type alias |
| Union of string values | Type alias |
| Tuple | Type alias |
| Mapped or conditional type | Type alias |
Explore 500+ free tutorials across 20+ languages and frameworks.