
Generated AI image by Microsoft Bing Image Creator
Introduction:
Since my new types of interesting engineering work have been mostly tied with backend and data engineering as of late, I’ve been earger to do a check in on what’s been happening lately in the web browser space and how much the world of web and front end development has changed since stepping away little over 18 months at the time of writing, particularly when working and manipulating with arrays of objects.
Sure enough, modern JavaScript and TypeScript, powered by the latest ECMAScript features, are loaded with powerful tools for working with arrays of objects. They enable concise, readable, and efficient code, making it simpler to manage, search, filter, and transform data structures. In this blog post, I’ll cover actionable tips and tricks to level up your expertise in handling arrays of objects, starting with some refresher basics up to something more advanced takes.
1. Iterate Like a Pro Using .map
, .forEach
, and Optional Chaining
When working with arrays of objects, you’ll often need to transform the elements or interact with their properties. The .map
method is great for returning a new array, while .forEach
is ideal for side effects like logging or modifying values.
Example:
// Array of objects
const users = [
{ id: 1, name: "Alice", age: 28 },
{ id: 2, name: "Bob", age: 34 },
{ id: 3, name: "Charlie", age: 25 },
];
// Transform with .map
const userNames = users.map((user) => user.name); // ['Alice', 'Bob', 'Charlie']
// Optional chaining to avoid undefined errors
const userAges = users.map((user) => user.age?.toString() || "Age unavailable"); // ['28', '34', '25']
console.log(userNames);
console.log(userAges);
2. Perform Efficient Search with .find
and .some
Finding a specific object or verifying the presence of a condition is effortless through .find
and .some
.
Example:
// Find specific user by ID
const user = users.find((user) => user.id === 2);
console.log(user); // { id: 2, name: 'Bob', age: 34 }
// Check if any user meets a condition
const hasOlderUser = users.some((user) => user.age > 30);
console.log(hasOlderUser); // true
3. Filter Arrays Easily with .filter
When you need to work with specific subsets, .filter is your best friend.
Example:
// Filter users by age condition
const youngUsers = users.filter((user) => user.age < 30);
console.log(youngUsers); // [{ id: 1, name: 'Alice', age: 28 }, { id: 3, name: 'Charlie', age: 25 }]
4. Sort Objects with .sort
Sorting an array based on specific object properties is straightforward with .sort
.
Example:
const sortedByAge = users.sort((a, b) => a.age - b.age);
console.log(sortedByAge);
// [{ id: 3, name: 'Charlie', age: 25 }, { id: 1, name: 'Alice', age: 28 }, { id: 2, name: 'Bob', age: 34 }]
Use .slice()
to prevent mutating the original array:
const sortedImmutable = users.slice().sort((a, b) => a.age - b.age);
5. Group Objects with .reduce
.reduce
isn’t just for summing numbers—it can group objects based on properties.
Example:
const usersByAge = users.reduce((acc, user) => {
const ageGroup = user.age < 30 ? "under30" : "over30";
(acc[ageGroup] ||= []).push(user);
return acc;
}, {} as Record<string, typeof users>);
console.log(usersByAge);
// {
// under30: [
// { id: 1, name: 'Alice', age: 28 },
// { id: 3, name: 'Charlie', age: 25 }
// ],
// over30: [
// { id: 2, name: 'Bob', age: 34 }
// ]
// }
6. Remove Duplicate Objects Using Sets and .map
Deduplicating arrays of objects based on specific properties can be achieved using Sets and .map.
Example:
const uniqueUsers = Array.from(
new Map(users.map((user) => [user.id, user])).values()
);
console.log(uniqueUsers);
// Deduplicates users based on their `id` property
7. Combine Multiple Arrays with Spread Syntax
The spread operator (...
) is an easy way to merge arrays of objects.
Example:
const moreUsers = [{ id: 4, name: "David", age: 32 }];
const allUsers = [...users, ...moreUsers];
console.log(allUsers);
// Merged array of objects
8. TypeScript Bonus Tips: Leverage Interfaces and Generics
TypeScript allows you to ensure type safety when working with arrays of objects. Use interfaces or types for clarity.
Example:
interface User {
id: number;
name: string;
age: number;
}
const typedUsers: User[] = [
{ id: 1, name: "Alice", age: 28 },
{ id: 2, name: "Bob", age: 34 },
{ id: 3, name: "Charlie", age: 25 },
];
// Access with proper autocomplete and safety
typedUsers.map((user) => user.name);
Generics in functions can add flexibility:
function getById<T extends { id: number }>(
array: T[],
id: number
): T | undefined {
return array.find((item) => item.id === id);
}
const user = getById(typedUsers, 2);
console.log(user);
9. Merge or Transform Properties Using the Spread Operator
The spread operator can be used to transform properties while keeping the original object intact.
Example:
// Update a single object while maintaining immutability
const updatedUsers = users.map((user) =>
user.id === 2 ? { ...user, name: "Robert" } : user
);
console.log(updatedUsers);
10. Handle Large Arrays with .flatMap
When an array of objects includes nested arrays, .flatMap is perfect for flattening arrays while applying map function.
Example:
const userActivities = [
{ id: 1, activities: ["running", "cycling"] },
{ id: 2, activities: ["swimming"] },
{ id: 3, activities: ["hiking", "climbing"] },
];
const flattenedActivities = userActivities.flatMap((user) => user.activities);
console.log(flattenedActivities); // ['running', 'cycling', 'swimming', 'hiking', 'climbing']
11. Handle Arrays with Nested Objects
An object may contain nested objects or nested arrays of related entities.
Example:
interface Employee {
id: number;
name: string;
department: {
name: string;
manager: string;
};
}
const employees: Employee[] = [
{ id: 1, name: "Alice", department: { name: "Engineering", manager: "Bob" } },
{
id: 2,
name: "Charlie",
department: { name: "Marketing", manager: "John" },
},
];
How to Handle:
- Use optional chaining (?.) to safely access nested properties:
const deptNames = employees.map((emp) => emp.department?.name); // ['Engineering', 'Marketing']
- Flatten or traverse nested structures using recursion or .flatMap() if necessary.
12. Handle Deeply Nested Arrays
Arrays inside objects, which themselves contain more arrays.
Example:
interface Team {
name: string;
members: { id: number; name: string }[];
}
const teams: Team[] = [
{
name: "Frontend",
members: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
],
},
{ name: "Backend", members: [{ id: 3, name: "Charlie" }] },
];
How to Handle:
- Flatten Nested Arrays using
.flatMap()
or recursion:
const allMembers = teams.flatMap((team) => team.members);
console.log(allMembers);
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]
13. Handle Arrays Containing Mixed Types
Objects in the array may follow different structures (discriminated union).
Example:
type Payment =
| { type: "card"; cardNumber: string }
| { type: "paypal"; email: string };
const payments: Payment[] = [
{ type: "card", cardNumber: "1234 5678 9012 3456" },
{ type: "paypal", email: "user@example.com" },
];
How to Handle:
- Use TypeScript discriminated unions or runtime checks:
payments.forEach((payment) => {
if (payment.type === "card") {
console.log("Card Payment: ", payment.cardNumber);
} else if (payment.type === "paypal") {
console.log("PayPal Email: ", payment.email);
}
});
14. Handle Associative Arrays (Key/Value Maps)
Objects in the array may act as pseudo-dictionary.
Example:
const keyValuePairs = [
{ key: "username", value: "Alice" },
{ key: "email", value: "alice@example.com" },
];
How to Handle:
- Convert to a normal object for fast lookup:
const dictionary = keyValuePairs.reduce((acc, pair) => {
acc[pair.key] = pair.value;
return acc;
}, {});
console.log(dictionary); // { username: 'Alice', email: 'alice@example.com' }
15. Explode Object’s enumerated Key/Value pairs into Arrays of data
Object’s enumerated key/value properties can be exploded into 3 types of arrays ie
- Array of Object’s individual Key/Value Pairs
- Array of Object’s Keys Only
- Array of Object’s Values Only
Example:
const someObjectWithKeyValuePairs = {
name: "Bob",
age: "44",
address: "Address 123",
};
How to Handle:
- Split object into array of object’s individual key/value pairs:
const arrOfObjectKeyValuePairs = Object.entries(someObjectWithKeyValuePairs).map(([key, value]) => ({
[key] : value
}))
console.log(arrOfObjectKeyValuePairs): // [{name: "Bob"}, {"age": "44"}, {"address":"Address 123"}]
- Split object into array of object’s keys only:
const arrOfObjectKeysOnly = Object.keys(arrOfObjectKeyValuePairs);
console.log(arrOfObjectKeysOnly); // ["name", "age", "address"]
- Split object into array of object’s values only:
const arrOfObjectValuesOnly = Object.values(arrOfObjectKeyValuePairs);
console.log(arrOfObjectValuesOnly); // ["Bob", "44", "Address 123"]
16. Enumerate Array of Object’s Key/Value pairs into Array of either Keys or Values only
Example:
const arrayOfObjectsKeyValuePair = [
{ integer: 1 },
{ double: 1.2 },
{ floating: 123e5 },
];
How to Handle:
- Fetch only the objects’ keys into new array. To do this, we iterate each item of array, explode item’s keys into array. Since the same array has only one key item, we can only fetch it on the zero-index.
const arrOfKeysOnly = arrayOfObjectsKeyValuePair.map((item) => Object.keys(item)[0])
console.log(arrOfKeysOnly): // ["integer", "double", "floating"]
- Fetch only the objects’ values into new array. To do this, we iterate each item of array, explode item’s value only into array. Since the same array has only one key item, we can only fetch it on the zero-index.
const arrOfValuesOnly = arrayOfObjectsKeyValuePair.map((item) => Object.values(item)[0])
console.log(arrOValuesOnly): // [1, 1.2, 123e5]
NB: Note this approach looks similar approach #14 and #15, but we reuse them for spliting array of objects into boths arrays of keys and values only respectively
17. Handle Circular References
Objects in the array may reference each other, creating circular dependencies.
Example:
const parent = { id: 1, name: "Parent" };
const child = { id: 2, name: "Child", parent };
parent.child = child; // Circular reference!
const family = [parent, child];
How to Handle:
- Serialize circular references (e.g., using flatted or a custom replacer):
import { stringify } from "flatted";
console.log(stringify(family));
18. Objects with Dynamic Properties
Each object may contain dynamic keys that are unpredictable.
Example:
const dynamicObjects = [
{ id: 1, meta: { created: "202-01-01", updated: "202-01-10" } },
{ id: 2, meta: { created: "202-05-01", deprecated: true } },
];
How to Handle:
- Use dot-notation, optional chaining, or dynamic property access:
const createdDates = dynamicObjects.map((obj) => obj.meta?.created);
19. Complex Relationships Between Objects
Objects reference or relate to other objects (relational data).
Example:
const users = [
{ id: 1, name: "Alice", friendIds: [2, 3] },
{ id: 2, name: "Bob", friendIds: [1] },
{ id: 3, name: "Charlie", friendIds: [1] },
];
How to Handle:
- Lookup relationships by ID:
const userMap = new Map(users.map((user) => [user.id, user]));
const getFriends = (userId: number) => {
const user = userMap.get(userId);
return user?.friendIds.map((id) => userMap.get(id));
};
console.log(getFriends(1));
// [{ id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]
20. API Pagination and Chunked Data
Arrays retrieved from APIs are paginated or chunked into parts.
Example:
const paginatedResponse = {
data: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
],
meta: { page: 1, totalPages: 5 },
};
How to Handle:
- Concatenate paginated data:
let allUsers = [];
for (let page = 1; page <= paginatedResponse.meta.totalPages; page++) {
const response = fetchData(page); // Simulated API call
allUsers = allUsers.concat(response.data);
}
21. Arrays with Computed Properties
Objects may include computed or derived properties.
Example:
const products = [
{ id: 1, name: "Laptop", price: 1000 },
{ id: 2, name: "Phone", price: 500 },
];
const productsWithTax = products.map((product) => ({
...product,
priceWithTax: product.price * 1.1, // Add10% tax
}));
Final Thoughts
Handling arrays of objects in JavaScript and TypeScript has never been easier with ECMAScript’s robust methods along TypeScript’s type safety features. Leveraging these modern tools helps me to write concise, maintainable, and efficient code once you have recognised numerous patterns of array data manipulation problems you’re going to deal with over and over again. Along with having solid fundamentals on Javascript arrays and primitive object types at its core gived you ultimate confidence to write succinct and clear Javascript array handling code where possible. Start incorporating these tips and tricks into your next project! It’s been fun learning experience.
Till then, Happy Coding!