Object Types
Most data in a TypeScript program flows through objects, so describing their shape precisely is the foundation of type safety. An object type lists the properties an object must have and the type of each one. You can write object types inline, give them a name with a type alias, or model them with an interface. This page covers the core syntax for declaring object shapes, how property types are checked, and when to reach for each style.
Inline object types
The simplest way to type an object is to write the shape directly in the annotation. This is handy for one-off parameters and local values where a named type would be overkill.
function printUser(user: { name: string; age: number }): void {
console.log(`${user.name} is ${user.age}`);
}
printUser({ name: "Ada", age: 36 });
// ❌ Error: Property 'age' is missing in type '{ name: string; }'.
printUser({ name: "Ada" });
Each member is separated by a semicolon (or comma — both are allowed). TypeScript requires every listed property to be present and correctly typed unless it is marked optional. Inline types are convenient but get noisy fast, so reuse them through a name once they appear more than once.
Naming a shape with a type alias
A type alias gives an object shape a reusable name. This is the most common way to model plain data structures, and the name shows up in editor tooltips and error messages.
type Point = {
x: number;
y: number;
};
const origin: Point = { x: 0, y: 0 };
function translate(p: Point, dx: number, dy: number): Point {
return { x: p.x + dx, y: p.y + dy };
}
Type aliases are not limited to objects — the same type keyword expresses unions, intersections, and mapped types. That flexibility makes them the default choice for data modeling. See Type Aliases for the full picture.
Modeling a shape with an interface
An interface declares an object shape too, with syntax that reads like an object literal of types. Interfaces are designed for extensible, object-oriented contracts: they support extends and can be reopened through declaration merging.
interface User {
id: number;
name: string;
email: string;
}
const admin: User = {
id: 1,
name: "Grace",
email: "[email protected]",
};
For a single object shape, interface and type are nearly interchangeable. Pick interface when you expect the type to be extended or implemented by classes, and type when you need union or mapped-type capabilities. The full trade-off lives in Interface vs Type.
type alias vs interface
| Capability | type alias | interface |
|---|---|---|
| Object shapes | Yes | Yes |
extends / inheritance | Via & intersection | Via extends |
| Declaration merging | No | Yes |
| Unions & mapped types | Yes | No |
| Implemented by a class | Yes | Yes |
| Best for | Data, unions, utilities | Public, extensible contracts |
Structural typing
TypeScript uses structural typing: compatibility is decided by shape, not by name. An object is assignable to an object type as long as it has at least the required properties with compatible types.
type Named = { name: string };
const dog = { name: "Rex", breed: "Husky" };
const n: Named = dog; // ✅ dog has a compatible `name`
function greet(x: Named) {
console.log(`Hi, ${x.name}`);
}
greet(dog); // ✅ extra `breed` is fine here
Because dog has a string property called name, it satisfies Named even though it carries an extra breed. This is why two unrelated types with identical shapes are interchangeable.
Structural typing is relaxed for variables, but object literals passed directly are subject to excess property checks — passing
{ name, breed }inline to aNamedparameter is an error. See Excess Property Checks.
The non-primitive object type
The lowercase object type means “any non-primitive value.” It accepts objects, arrays, and functions but rejects string, number, boolean, and friends. It is far weaker than a real shape because it exposes no properties.
function logKeys(value: object) {
console.log(Object.keys(value));
}
logKeys({ a: 1 }); // ✅
logKeys([1, 2]); // ✅ arrays are objects
// ❌ Error: Argument of type 'number' is not assignable to parameter of type 'object'.
logKeys(42);
Use object only when you genuinely accept any non-primitive and do not need its members. Whenever you know the properties, prefer a concrete shape. The distinction between object, {}, and Object is covered in The object Type.
Best Practices
- Use a named
typeorinterfaceonce a shape appears in more than one place. - Prefer
interfacefor extensible, class-implemented contracts;typefor data and unions. - Keep inline object types small — extract them to a name as soon as they grow.
- Rely on structural typing, but remember object literals trigger excess property checks.
- Avoid the bare
objecttype when you actually know the properties; declare the shape instead. - Name shapes after the concept they model (
User,Point), not after their usage site. - Let inference type local objects, and annotate at function boundaries for clear contracts.