Recently, I was working on a TypeScript project where I needed to manage a collection of user profiles with various properties. The challenge was to ensure type safety while still maintaining flexibility. The solution? TypeScript arrays of objects.
In this tutorial, I’ll cover everything you need to know about working with arrays of objects in TypeScript, including declaration, manipulation, and best practices.
So let’s dive in!
Declare Arrays of Objects in TypeScript
Working with arrays of objects in TypeScript is one of the most common tasks you’ll encounter in frontend and backend development. When I first started with TypeScript, I realized that proper typing for object arrays saves countless hours of debugging.
Here are the ways you can declare an array of objects in TypeScript:
Using Type Interfaces for Object Arrays
The most common and recommended approach is to define an interface for your objects first:
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
// Creating an array of User objects
const users: User[] = [
{ id: 1, name: 'John Smith', email: 'john@example.com', isActive: true },
{ id: 2, name: 'Sarah Johnson', email: 'sarah@example.com', isActive: false }
];
In this example, I’ve created a User interface that defines the structure of each object in our array. The User[] syntax tells TypeScript that we’re creating an array where each element must conform to the User interface.
I use this approach in most of my projects because it gives me clear type definitions that can be reused throughout the codebase.
Using Type Aliases
Another approach is to use type aliases:
type Product = {
id: number;
title: string;
price: number;
inStock: boolean;
};
// Creating an array of Product objects
const products: Product[] = [
{ id: 101, title: 'iPhone 13', price: 999, inStock: true },
{ id: 102, title: 'MacBook Pro', price: 1999, inStock: false }
];
Type aliases work similarly to interfaces but use a different syntax. For simple object types, they function almost identically to interfaces. The choice between them often comes down to personal preference.
Inline Type Declarations
For simpler cases, you can define the type inline:
const states: { name: string; abbreviation: string; population: number }[] = [
{ name: 'California', abbreviation: 'CA', population: 39.5 },
{ name: 'Texas', abbreviation: 'TX', population: 29.1 },
{ name: 'Florida', abbreviation: 'FL', population: 21.5 }
];
Here, I’m defining the object structure directly in the variable declaration. This approach works well for quick, one-off uses but becomes unwieldy for complex objects or when you need to reuse the type.

Check out How to Iterate Over a JSON Object in TypeScript
Manipulate TypeScript Arrays of Objects
Once you’ve declared your array of objects, you’ll need to manipulate it. Here are some common operations:
Adding Elements to an Object Array
interface Task {
id: number;
title: string;
completed: boolean;
}
const tasks: Task[] = [
{ id: 1, title: 'Complete TypeScript tutorial', completed: false }
];
// Adding a new task at the end
tasks.push({ id: 2, title: 'Review PR', completed: false });
// Adding at the beginning
tasks.unshift({ id: 0, title: 'Check emails', completed: true });
The push() method adds elements to the end of an array, while unshift() adds them to the beginning. Notice how TypeScript ensures that the objects we’re adding match the Task interface.
Removing Elements from an Object Array
// Remove the last task
const lastTask = tasks.pop();
// Remove the first task
const firstTask = tasks.shift();
// Remove a specific task by filtering
const filteredTasks = tasks.filter(task => task.id !== 1);
The pop() method removes the last element, shift() removes the first element, and filter() creates a new array without the elements that don’t pass the test function. The filter() method is particularly useful when you want to remove elements based on certain conditions.
Finding Elements in an Object Array
// Find a specific task
const task = tasks.find(task => task.id === 2);
// Check if a task exists
const taskExists = tasks.some(task => task.title.includes('TypeScript'));
// Get the index of a specific task
const taskIndex = tasks.findIndex(task => task.id === 1);
The find() method returns the first element that satisfies the testing function, some() checks if at least one element meets the condition, and findIndex() returns the index of the first element that meets the condition.
Check out How to Convert TypeScript Objects to JSON
Advanced Techniques for Object Arrays in TypeScript
Working with Optional Properties
In real-world scenarios, you often need to work with objects that might not have all properties:
interface Customer {
id: number;
name: string;
email: string;
address?: {
street: string;
city: string;
state: string;
zip: string;
};
}
const customers: Customer[] = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{
id: 2,
name: 'Jane Smith',
email: 'jane@example.com',
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zip: '10001'
}
}
];
The question mark (?) after address indicates that this property is optional. This means some customers might have an address, while others might not. This flexibility is crucial when working with real data.
Using Union Types in Object Arrays
Sometimes you need an array that can contain different types of objects:
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number };
const shapes: Shape[] = [
{ kind: 'circle', radius: 5 },
{ kind: 'rectangle', width: 10, height: 20 }
];
// Type narrowing when working with the array
shapes.forEach(shape => {
if (shape.kind === 'circle') {
console.log(`Circle with radius ${shape.radius}`);
} else {
console.log(`Rectangle with dimensions ${shape.width}x${shape.height}`);
}
});
This is a powerful TypeScript feature called discriminated unions. The kind property acts as a discriminator that helps TypeScript understand which type of shape we’re working with. Notice how TypeScript narrows down the type in the if-else blocks.
Create Readonly Object Arrays
When you want to prevent modifications to your array:
const USStates: ReadonlyArray<{ name: string; capital: string }> = [
{ name: 'New York', capital: 'Albany' },
{ name: 'California', capital: 'Sacramento' },
{ name: 'Texas', capital: 'Austin' }
];
// This would cause a TypeScript error
// USStates.push({ name: 'Florida', capital: 'Tallahassee' });
ReadonlyArray<T> is a built-in TypeScript type that prevents mutations to the array. This is useful when you want to ensure that certain data remains constant throughout your application.
Check out How to Import JSON Files in TypeScript?
Common Patterns and Best Practices
Transforming Object Arrays
interface Employee {
id: number;
name: string;
department: string;
salary: number;
}
const employees: Employee[] = [
{ id: 1, name: 'Alice Johnson', department: 'Engineering', salary: 120000 },
{ id: 2, name: 'Bob Smith', department: 'Marketing', salary: 95000 },
{ id: 3, name: 'Carol Davis', department: 'Engineering', salary: 110000 }
];
// Map to get just the names
const employeeNames: string[] = employees.map(emp => emp.name);
// Calculate total salary
const totalSalary = employees.reduce((sum, emp) => sum + emp.salary, 0);
// Group by department
const byDepartment = employees.reduce((acc, emp) => {
acc[emp.department] = acc[emp.department] || [];
acc[emp.department].push(emp);
return acc;
}, {} as Record<string, Employee[]>);
The map() method creates a new array by transforming each element. Here, we’re extracting just the names from our employee objects.
The reduce() method is incredibly versatile. In the first example, we’re using it to sum up all salaries. In the second example, we’re using it to group employees by department.
Sorting Arrays of Objects
// Sort employees by salary (highest first)
const sortedBySalary = [...employees].sort((a, b) => b.salary - a.salary);
// Sort by name
const sortedByName = [...employees].sort((a, b) => a.name.localeCompare(b.name));
The sort() method allows us to arrange our array elements based on different criteria. Note that I’m using the spread operator (...) to create a copy of the array before sorting, as sort() modifies the original array.
The localeCompare() method is especially useful for sorting strings in a locale-aware manner.
Check out TypeScript Enum with Multiple Values
Handling Arrays of Objects in Functions
When working with functions that accept or return arrays of objects, proper typing is crucial:
interface Order {
id: string;
customerName: string;
items: { productId: string; quantity: number; price: number }[];
total: number;
date: Date;
}
// Function that returns an array of objects
function getRecentOrders(days: number): Order[] {
// Implementation...
return [
{
id: 'ORD-12345',
customerName: 'Michael Johnson',
items: [{ productId: 'P-100', quantity: 2, price: 29.99 }],
total: 59.98,
date: new Date('2023-05-15')
}
];
}
// Function that filters an array of objects
function filterOrdersByTotal(orders: Order[], minTotal: number): Order[] {
return orders.filter(order => order.total >= minTotal);
}
// Function with a callback that processes objects
function processOrders(
orders: Order[],
processor: (order: Order) => void
): void {
orders.forEach(processor);
}
These examples show different ways of working with object arrays in functions:
getRecentOrders()returns an array ofOrderobjectsfilterOrdersByTotal()takes an array of orders and returns a filtered arrayprocessOrders()takes an array and a callback function that processes each order
By properly typing these functions, we ensure that they’ll be used correctly throughout our codebase.
Read How to Push Objects into an Array in TypeScript?
Error Handling with Object Arrays
When working with arrays of objects, especially from external sources like APIs, proper error handling is essential:
interface ApiResponse<T> {
data: T[];
error: string | null;
status: number;
}
// Function to fetch users from an API
async function fetchUsers(): Promise<ApiResponse<User>> {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
return {
data: data as User[],
error: null,
status: response.status
};
} catch (error) {
return {
data: [],
error: error instanceof Error ? error.message : 'Unknown error',
status: 500
};
}
}
// Using the function
async function displayUsers() {
const result = await fetchUsers();
if (result.error) {
console.error(`Failed to fetch users: ${result.error}`);
return;
}
// Process the users array
result.data.forEach(user => {
console.log(`User: ${user.name}, Email: ${user.email}`);
});
}
In this example, I’ve created a generic ApiResponse<T> interface that can wrap any array of objects. The fetchUsers() function handles potential errors and provides a consistent response structure, making error handling more predictable throughout the application.
Check out Get String Between 2 Characters In TypeScript
Type Guards for TypeScript Object Arrays
When working with complex object arrays, type guards help ensure type safety:
interface AdminUser {
id: number;
name: string;
role: 'admin';
permissions: string[];
}
interface RegularUser {
id: number;
name: string;
role: 'user';
subscription: 'free' | 'premium';
}
type SystemUser = AdminUser | RegularUser;
const users: SystemUser[] = [
{ id: 1, name: 'Alice', role: 'admin', permissions: ['read', 'write', 'delete'] },
{ id: 2, name: 'Bob', role: 'user', subscription: 'premium' }
];
// Type guard function
function isAdminUser(user: SystemUser): user is AdminUser {
return user.role === 'admin';
}
// Using the type guard
users.forEach(user => {
if (isAdminUser(user)) {
// TypeScript knows this is an AdminUser
console.log(`Admin ${user.name} has permissions: ${user.permissions.join(', ')}`);
} else {
// TypeScript knows this is a RegularUser
console.log(`User ${user.name} has a ${user.subscription} subscription`);
}
});
Here, I’ve created a type guard isAdminUser() that helps TypeScript understand which specific type we’re working with. This is especially useful when dealing with arrays containing different object types.
Performance Considerations
When working with large arrays of objects, performance becomes important:
interface Transaction {
id: string;
amount: number;
date: Date;
category: string;
}
const transactions: Transaction[] = [
/* Imagine thousands of transactions here */
];
// INEFFICIENT way to find transactions by category
function getTransactionsByCategory(category: string): Transaction[] {
return transactions.filter(t => t.category === category);
}
// MORE EFFICIENT approach using an index
const transactionsByCategory: Record<string, Transaction[]> = {};
// Build the index once
transactions.forEach(transaction => {
if (!transactionsByCategory[transaction.category]) {
transactionsByCategory[transaction.category] = [];
}
transactionsByCategory[transaction.category].push(transaction);
});
// Fast lookup by category
function getTransactionsByCategoryFast(category: string): Transaction[] {
return transactionsByCategory[category] || [];
}
In this example, I’ve demonstrated how creating an index can dramatically improve performance when querying a large array of objects frequently. This pattern is similar to database indexing and can significantly improve the responsiveness of your application.
Immutable Operations on Object Arrays
When working with state management libraries or frameworks like React, immutable operations are preferred:
interface TodoItem {
id: number;
text: string;
completed: boolean;
}
const todos: TodoItem[] = [
{ id: 1, text: 'Learn TypeScript', completed: false },
{ id: 2, text: 'Build a project', completed: false },
{ id: 3, text: 'Write documentation', completed: true }
];
// Immutably toggle a todo's completed status
function toggleTodo(todos: TodoItem[], id: number): TodoItem[] {
return todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
);
}
// Usage
const updatedTodos = toggleTodo(todos, 1);
console.log(updatedTodos);
// Original array remains unchanged
console.log(todos);
In this example, I’m using the map() method along with the spread operator to create a new array with one modified object, rather than modifying the original array. This immutable approach helps prevent bugs in applications where state changes need to be predictable and trackable.
Nested Object Arrays
Working with nested arrays of objects is a common scenario in complex applications:
interface Department {
id: number;
name: string;
employees: Employee[];
}
const departments: Department[] = [
{
id: 1,
name: 'Engineering',
employees: [
{ id: 101, name: 'Alice', salary: 120000 },
{ id: 102, name: 'Bob', salary: 110000 }
]
},
{
id: 2,
name: 'Marketing',
employees: [
{ id: 201, name: 'Carol', salary: 95000 }
]
}
];
// Finding an employee across all departments
function findEmployee(employeeId: number): Employee | undefined {
for (const dept of departments) {
const employee = dept.employees.find(emp => emp.id === employeeId);
if (employee) return employee;
}
return undefined;
}
// Flattening nested arrays
const allEmployees = departments.flatMap(dept => dept.employees);
console.log(allEmployees);
In this example, I’m working with a nested structure where each department has its own array of employees. The flatMap() method is particularly useful for transforming and flattening nested arrays in a single operation.
In this tutorial, I explained how to work with an array of objects in TypeScript. I hope you found this article helpful! If you have any questions about working with arrays of objects in TypeScript, feel free to leave them in the comments below.
You may also like the following tutorials:
- Convert String To Double In TypeScript
- Get String After Character In TypeScript
- Sort Arrays of Objects in TypeScript

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.