Skip to content

TypeScript - Objects

Objects are fundamental in JavaScript and TypeScript for grouping related data and functionality. They represent collections of key-value pairs, where keys are typically strings (or Symbols) and values can be anything: primitives (strings, numbers, booleans), functions (methods), arrays, or even other objects.

The most common way to create objects is using object literal syntax {}.

// Object literal syntax
const car = {
make: "Toyota",
model: "Camry",
year: 2022,
start: function() { // Method using traditional function syntax
console.log(`Starting the ${this.make} ${this.model}...`);
},
stop() { // Shorthand method syntax (ES6)
console.log(`Stopping the ${this.make} ${this.model}.`);
},
features: ["Air Conditioning", "Cruise Control"] // Value can be an array
};
// Accessing properties using dot notation
console.log(car.make); // Output: Toyota
console.log(car.year); // Output: 2202
// Accessing properties using bracket notation (useful for dynamic keys)
const propertyName = "model";
console.log(car[propertyName]); // Output: Camry
// Calling methods
car.start();
car.stop();
console.log(car.features[0]); // Output: Air Conditioning

TypeScript allows you to define the shape or structure of objects, ensuring type safety when accessing properties or methods. There are several ways to type objects:

When you create an object literal and assign it to a variable, TypeScript infers the types of its properties.

const inferredUser = {
id: 101,
username: "alice",
isActive: true
};
// TypeScript infers the type as: { id: number; username: string; isActive: boolean; }
console.log(inferredUser.username.toUpperCase()); // OK
// inferredUser.id = "user-101"; // Error: Type 'string' is not assignable to type 'number'.

Using an interface is a common and idiomatic way to define the structure of an object explicitly.

interface Product {
readonly sku: string;
name: string;
price: number;
description?: string; // Optional property
display(): void;
}
const book: Product = {
sku: "BK-123",
name: "The TypeScript Handbook",
price: 29.99,
// description is optional
display() {
console.log(`${this.name} ($${this.price}) - SKU: ${this.sku}`);
}
};
book.display();
// book.sku = "BK-456"; // Error: Cannot assign to 'sku' because it is a read-only property.

Using a type alias provides another way to define object shapes.

type Point = {
x: number;
y: number;
};
const origin: Point = { x: 0, y: 0 };
function calculateDistance(p1: Point, p2: Point): number {
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
return Math.sqrt(dx * dx + dy * dy);
}
const point1: Point = { x: 3, y: 4 };
console.log(`Distance from origin: ${calculateDistance(origin, point1)}`); // Output: 5

You can define the shape directly in type annotations, often used for function parameters or return types when a reusable interface/type isn’t needed.

function processCoords(coords: { lat: number; lon: number }): void {
console.log(`Latitude: ${coords.lat}, Longitude: ${coords.lon}`);
}
processCoords({ lat: 40.7128, lon: -74.0060 });
// processCoords({ latitude: 40.7, longitude: -74 }); // Error: Object literal may only specify known properties...

Passing objects as parameters is very common. Use interfaces, type aliases, or anonymous types to define the expected structure of the parameter object.

interface UserProfile {
userId: string;
email: string;
displayName?: string;
}
function displayUserProfile(profile: UserProfile): void {
console.log(`User ID: ${profile.userId}`);
console.log(`Email: ${profile.email}`);
if (profile.displayName) {
console.log(`Display Name: ${profile.displayName}`);
}
}
const user: UserProfile = { userId: 'u-001', email: 'user@example.com', displayName: 'Test User' };
const userWithoutDisplayName: UserProfile = { userId: 'u-002', email: 'another@example.com' };
displayUserProfile(user);
displayUserProfile(userWithoutDisplayName);

TypeScript uses a structural type system. This means that if an object has at least the properties and methods required by a type (like an interface), TypeScript considers it compatible with that type, regardless of whether it explicitly implements the interface. This is often called “duck typing”.

“If it walks like a duck and quacks like a duck, then it must be a duck.”

interface Named {
name: string;
}
let obj1 = { name: "My Object", value: 10 }; // Has 'name' property
let obj2 = { title: "Another Object", name: "Named Item" }; // Has 'name' property
let obj3 = { id: 123 }; // Does NOT have 'name' property
function printName(item: Named): void {
console.log(`Name: ${item.name}`);
}
printName(obj1); // OK - obj1 has a 'name: string' property
printName(obj2); // OK - obj2 has a 'name: string' property
// printName(obj3); // Error: Argument of type '{ id: number; }' is not assignable to parameter of type 'Named'. Property 'name' is missing...

This flexibility allows interoperability between different object structures as long as they share the required shape.