Beyond basic array and object operations lie powerful patterns for manipulating complex data structures. From deep cloning nested objects to flattening multi-dimensional arrays, from grouping data by properties to transforming deeply nested structures, these advanced patterns are essential for real-world applications. Whether processing API responses, managing application state, or transforming data for visualization, mastering these patterns makes you a more effective JavaScript developer. Let's explore advanced data manipulation!
Deep Cloning
The Problem with Shallow Copies
// Shallow copy - nested objects still share references
let original = {
name: 'Alice',
age: 25,
address: {
city: 'NYC',
zip: '10001'
},
hobbies: ['reading', 'coding']
};
// Shallow copy with spread
let shallowCopy = { ...original };
// Top-level properties are independent
shallowCopy.name = 'Bob';
console.log(original.name); // "Alice" (unchanged)
// But nested objects are still shared!
shallowCopy.address.city = 'LA';
console.log(original.address.city); // "LA" (changed!)
shallowCopy.hobbies.push('gaming');
console.log(original.hobbies); // ['reading', 'coding', 'gaming'] (changed!)
// Same problem with Object.assign()
let shallowCopy2 = Object.assign({}, original);
shallowCopy2.address.zip = '90001';
console.log(original.address.zip); // "90001" (changed!)Deep Clone Solutions
// Method 1: structuredClone() - Modern, best solution
let original = {
name: 'Alice',
age: 25,
address: {
city: 'NYC',
zip: '10001'
},
hobbies: ['reading', 'coding'],
createdAt: new Date()
};
let deepCopy = structuredClone(original);
// Completely independent
deepCopy.address.city = 'LA';
deepCopy.hobbies.push('gaming');
console.log(original.address.city); // "NYC" (unchanged!)
console.log(original.hobbies); // ['reading', 'coding'] (unchanged!)
console.log(deepCopy.createdAt instanceof Date); // true (dates preserved!)
// Method 2: JSON.parse(JSON.stringify()) - Simple but limited
let original2 = {
name: 'Bob',
data: {
nested: {
value: 42
}
}
};
let deepCopy2 = JSON.parse(JSON.stringify(original2));
deepCopy2.data.nested.value = 100;
console.log(original2.data.nested.value); // 42 (unchanged!)
// BUT: JSON method has limitations
let problematic = {
date: new Date(), // Becomes string
func: () => 'hi', // Lost
undef: undefined, // Lost
symbol: Symbol('id'), // Lost
map: new Map([['a', 1]]) // Lost
};
let copy = JSON.parse(JSON.stringify(problematic));
console.log(copy.date instanceof Date); // false (became string!)
console.log(copy.func); // undefined (lost!)
// Method 3: Manual recursive deep clone
function deepClone(obj) {
// Handle primitives and null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle Date
if (obj instanceof Date) {
return new Date(obj);
}
// Handle Array
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
// Handle Object
let clone = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
// Usage
let complex = {
name: 'Charlie',
date: new Date(),
nested: {
deep: {
value: [1, 2, { a: 3 }]
}
}
};
let cloned = deepClone(complex);
cloned.nested.deep.value[2].a = 999;
console.log(complex.nested.deep.value[2].a); // 3 (unchanged!)structuredClone handles deep nesting
Shallow Copy Problem
// Shallow copy problem
let user = {
name: 'Alice',
settings: {
theme: 'dark'
}
};
let copy = { ...user };
copy.settings.theme = 'light';
// Original changed!
console.log(user.settings.theme);
// "light"
// Manual nested copying
let copy2 = {
...user,
settings: { ...user.settings }
};
// Tedious for deep nesting!Deep Clone Solution
// Deep clone solution
let user = {
name: 'Alice',
settings: {
theme: 'dark'
}
};
let copy = structuredClone(user);
copy.settings.theme = 'light';
// Original unchanged!
console.log(user.settings.theme);
// "dark"
// One line, works for any depth!Nested Destructuring
// Extract deeply nested values
let user = {
name: 'Alice',
age: 25,
address: {
street: '123 Main St',
city: 'NYC',
coordinates: {
lat: 40.7128,
lng: -74.0060
}
},
friends: [
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 28 }
]
};
// Nested object destructuring
let {
name,
address: {
city,
coordinates: { lat, lng }
}
} = user;
console.log(name); // "Alice"
console.log(city); // "NYC"
console.log(lat); // 40.7128
console.log(lng); // -74.0060
// Nested array destructuring
let {
friends: [firstFriend, secondFriend]
} = user;
console.log(firstFriend.name); // "Bob"
console.log(secondFriend.age); // 28
// Extract specific nested array properties
let {
friends: [
{ name: friend1Name },
{ name: friend2Name }
]
} = user;
console.log(friend1Name); // "Bob"
console.log(friend2Name); // "Charlie"
// With default values
let {
address: {
country = 'USA',
zipCode = '00000'
} = {}
} = user;
console.log(country); // "USA" (default)
console.log(zipCode); // "00000" (default)
// Function parameters with nested destructuring
function displayUserLocation({ address: { city, coordinates: { lat, lng } } }) {
console.log(`${city}: (${lat}, ${lng})`);
}
displayUserLocation(user); // "NYC: (40.7128, -74.006)"
// Practical: API response
let apiResponse = {
status: 'success',
data: {
user: {
id: 123,
profile: {
name: 'Alice',
email: 'alice@test.com'
}
},
posts: [
{ id: 1, title: 'First Post' },
{ id: 2, title: 'Second Post' }
]
}
};
let {
status,
data: {
user: {
profile: { name: userName, email }
},
posts: [firstPost]
}
} = apiResponse;
console.log(userName); // "Alice"
console.log(firstPost.title); // "First Post"Flattening Arrays
// Nested arrays
let nested = [1, 2, [3, 4, [5, 6, [7, 8]]]];
// flat() method - flatten one level
let flat1 = nested.flat();
console.log(flat1); // [1, 2, 3, 4, [5, 6, [7, 8]]]
// flat(2) - flatten two levels
let flat2 = nested.flat(2);
console.log(flat2); // [1, 2, 3, 4, 5, 6, [7, 8]]
// flat(Infinity) - flatten all levels
let flatAll = nested.flat(Infinity);
console.log(flatAll); // [1, 2, 3, 4, 5, 6, 7, 8]
// flatMap() - map then flatten (one level)
let numbers = [1, 2, 3, 4];
// Without flatMap
let mapped = numbers.map(n => [n, n * 2]);
console.log(mapped); // [[1, 2], [2, 4], [3, 6], [4, 8]]
let flattened = mapped.flat();
console.log(flattened); // [1, 2, 2, 4, 3, 6, 4, 8]
// With flatMap (one operation)
let result = numbers.flatMap(n => [n, n * 2]);
console.log(result); // [1, 2, 2, 4, 3, 6, 4, 8]
// Practical: Extract nested values
let users = [
{ name: 'Alice', hobbies: ['reading', 'coding'] },
{ name: 'Bob', hobbies: ['gaming', 'cooking'] },
{ name: 'Charlie', hobbies: ['music'] }
];
// Get all hobbies (flattened)
let allHobbies = users.flatMap(user => user.hobbies);
console.log(allHobbies);
// ['reading', 'coding', 'gaming', 'cooking', 'music']
// Split and flatten sentences
let sentences = ['Hello world', 'How are you'];
let words = sentences.flatMap(s => s.split(' '));
console.log(words);
// ['Hello', 'world', 'How', 'are', 'you']
// Custom flatten function (recursive)
function flattenDeep(arr) {
return arr.reduce((flat, item) => {
return flat.concat(
Array.isArray(item) ? flattenDeep(item) : item
);
}, []);
}
let deep = [1, [2, [3, [4, [5]]]]];
console.log(flattenDeep(deep)); // [1, 2, 3, 4, 5]Grouping Data
// Group array items by property
let products = [
{ name: 'Laptop', category: 'Electronics', price: 999 },
{ name: 'Phone', category: 'Electronics', price: 699 },
{ name: 'Desk', category: 'Furniture', price: 299 },
{ name: 'Chair', category: 'Furniture', price: 199 },
{ name: 'Mouse', category: 'Electronics', price: 25 }
];
// Group by category using reduce
let byCategory = products.reduce((groups, product) => {
let category = product.category;
if (!groups[category]) {
groups[category] = [];
}
groups[category].push(product);
return groups;
}, {});
console.log(byCategory);
// {
// Electronics: [
// { name: 'Laptop', ... },
// { name: 'Phone', ... },
// { name: 'Mouse', ... }
// ],
// Furniture: [
// { name: 'Desk', ... },
// { name: 'Chair', ... }
// ]
// }
// Reusable groupBy function
function groupBy(array, key) {
return array.reduce((groups, item) => {
let value = typeof key === 'function' ? key(item) : item[key];
if (!groups[value]) {
groups[value] = [];
}
groups[value].push(item);
return groups;
}, {});
}
// Group by category
let grouped1 = groupBy(products, 'category');
// Group by price range (using function)
let grouped2 = groupBy(products, product => {
if (product.price < 100) return 'cheap';
if (product.price < 500) return 'medium';
return 'expensive';
});
console.log(grouped2);
// {
// expensive: [{ name: 'Laptop', ... }, { name: 'Phone', ... }],
// medium: [{ name: 'Desk', ... }, { name: 'Chair', ... }],
// cheap: [{ name: 'Mouse', ... }]
// }
// Group and transform
let users = [
{ name: 'Alice', age: 25, city: 'NYC' },
{ name: 'Bob', age: 30, city: 'NYC' },
{ name: 'Charlie', age: 28, city: 'LA' },
{ name: 'David', age: 35, city: 'LA' }
];
// Group by city and get names only
let namesByCity = users.reduce((groups, user) => {
if (!groups[user.city]) {
groups[user.city] = [];
}
groups[user.city].push(user.name);
return groups;
}, {});
console.log(namesByCity);
// { NYC: ['Alice', 'Bob'], LA: ['Charlie', 'David'] }
// Group and count
let counts = users.reduce((counts, user) => {
counts[user.city] = (counts[user.city] || 0) + 1;
return counts;
}, {});
console.log(counts); // { NYC: 2, LA: 2 }Advanced Transformations
Pivot Data
// Transform array of objects to different structure
let sales = [
{ product: 'Laptop', month: 'Jan', amount: 5000 },
{ product: 'Laptop', month: 'Feb', amount: 6000 },
{ product: 'Phone', month: 'Jan', amount: 3000 },
{ product: 'Phone', month: 'Feb', amount: 3500 }
];
// Pivot: products as keys, months as nested keys
function pivot(data, rowKey, colKey, valueKey) {
return data.reduce((result, item) => {
let row = item[rowKey];
let col = item[colKey];
let value = item[valueKey];
if (!result[row]) {
result[row] = {};
}
result[row][col] = value;
return result;
}, {});
}
let pivoted = pivot(sales, 'product', 'month', 'amount');
console.log(pivoted);
// {
// Laptop: { Jan: 5000, Feb: 6000 },
// Phone: { Jan: 3000, Feb: 3500 }
// }
// Access: pivoted.Laptop.Jan -> 5000Merge Arrays of Objects
// Merge arrays by matching ID
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
let orders = [
{ userId: 1, orderId: 101, total: 99 },
{ userId: 1, orderId: 102, total: 149 },
{ userId: 2, orderId: 103, total: 79 }
];
// Add orders to users
let usersWithOrders = users.map(user => ({
...user,
orders: orders.filter(order => order.userId === user.id)
}));
console.log(usersWithOrders);
// [
// {
// id: 1,
// name: 'Alice',
// orders: [
// { userId: 1, orderId: 101, total: 99 },
// { userId: 1, orderId: 102, total: 149 }
// ]
// },
// {
// id: 2,
// name: 'Bob',
// orders: [{ userId: 2, orderId: 103, total: 79 }]
// }
// ]
// Merge two arrays by ID
let array1 = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
let array2 = [
{ id: 1, age: 25 },
{ id: 2, age: 30 }
];
function mergeById(arr1, arr2, key = 'id') {
return arr1.map(item1 => {
let item2 = arr2.find(item => item[key] === item1[key]);
return { ...item1, ...item2 };
});
}
let merged = mergeById(array1, array2);
console.log(merged);
// [
// { id: 1, name: 'Alice', age: 25 },
// { id: 2, name: 'Bob', age: 30 }
// ]Chunk Array
// Split array into chunks
function chunk(array, size) {
let chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let chunks = chunk(numbers, 3);
console.log(chunks);
// [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// Practical: Paginate items
function paginate(items, itemsPerPage) {
return chunk(items, itemsPerPage);
}
let allItems = Array.from({ length: 25 }, (_, i) => `Item ${i + 1}`);
let pages = paginate(allItems, 10);
console.log(`Total pages: ${pages.length}`); // 3
console.log(`Page 1:`, pages[0].length); // 10 items
console.log(`Page 3:`, pages[2].length); // 5 itemsRemove Duplicates from Array of Objects
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' }, // Duplicate
{ id: 3, name: 'Charlie' },
{ id: 2, name: 'Bob' } // Duplicate
];
// Remove duplicates by ID
function uniqueBy(array, key) {
let seen = new Set();
return array.filter(item => {
let value = typeof key === 'function' ? key(item) : item[key];
if (seen.has(value)) {
return false;
}
seen.add(value);
return true;
});
}
let unique = uniqueBy(users, 'id');
console.log(unique);
// [
// { id: 1, name: 'Alice' },
// { id: 2, name: 'Bob' },
// { id: 3, name: 'Charlie' }
// ]
// Alternative: Using Map
function uniqueById(array) {
let map = new Map();
array.forEach(item => {
if (!map.has(item.id)) {
map.set(item.id, item);
}
});
return Array.from(map.values());
}
let unique2 = uniqueById(users);
console.log(unique2.length); // 3Practical Examples
Example 1: Data Aggregation
// Aggregate sales data
let transactions = [
{ product: 'Laptop', category: 'Electronics', amount: 999, quantity: 1 },
{ product: 'Phone', category: 'Electronics', amount: 699, quantity: 2 },
{ product: 'Desk', category: 'Furniture', amount: 299, quantity: 1 },
{ product: 'Mouse', category: 'Electronics', amount: 25, quantity: 5 },
{ product: 'Chair', category: 'Furniture', amount: 199, quantity: 2 }
];
// Calculate totals by category
function aggregateByCategory(transactions) {
return transactions.reduce((summary, trans) => {
let cat = trans.category;
if (!summary[cat]) {
summary[cat] = {
totalAmount: 0,
totalQuantity: 0,
products: []
};
}
summary[cat].totalAmount += trans.amount * trans.quantity;
summary[cat].totalQuantity += trans.quantity;
summary[cat].products.push(trans.product);
return summary;
}, {});
}
let summary = aggregateByCategory(transactions);
console.log(summary);
// {
// Electronics: {
// totalAmount: 2522,
// totalQuantity: 8,
// products: ['Laptop', 'Phone', 'Mouse']
// },
// Furniture: {
// totalAmount: 697,
// totalQuantity: 3,
// products: ['Desk', 'Chair']
// }
// }Example 2: Nested Data Transformation
// Transform flat data to hierarchical
let flatData = [
{ id: 1, name: 'Electronics', parentId: null },
{ id: 2, name: 'Computers', parentId: 1 },
{ id: 3, name: 'Phones', parentId: 1 },
{ id: 4, name: 'Laptops', parentId: 2 },
{ id: 5, name: 'Desktops', parentId: 2 }
];
function buildTree(flatData, parentId = null) {
return flatData
.filter(item => item.parentId === parentId)
.map(item => ({
...item,
children: buildTree(flatData, item.id)
}));
}
let tree = buildTree(flatData);
console.log(JSON.stringify(tree, null, 2));
// [
// {
// id: 1,
// name: 'Electronics',
// parentId: null,
// children: [
// {
// id: 2,
// name: 'Computers',
// parentId: 1,
// children: [
// { id: 4, name: 'Laptops', ... },
// { id: 5, name: 'Desktops', ... }
// ]
// },
// {
// id: 3,
// name: 'Phones',
// parentId: 1,
// children: []
// }
// ]
// }
// ]Advanced Array & Object Patterns
Master complex data manipulation
console.log() to see your output in the console above.Key Takeaways
structuredClone()creates true deep copies- Shallow copies share references to nested objects
- Nested destructuring extracts deeply nested values
flat()flattens nested arraysflatMap()maps then flattens in one operation- Use
reduce()to group array items by properties - Pivot transforms row data to columnar structure
- Merge arrays by matching IDs or keys
- Chunk splits arrays for pagination
- Use Set or Map to remove duplicates from object arrays
What's Next?
You now understand advanced array and object patterns! You've learned deep cloning, nested destructuring, flattening, grouping, and complex data transformations. These patterns are essential for real-world application development.
In the next lesson, we'll explore Regular Expressions Basics—learning pattern matching for validating, searching, and manipulating strings!
💪 Practice Challenge:
Before moving on, try:
- Deep clone a complex nested object
- Use nested destructuring to extract API response data
- Flatten a multi-level nested array
- Group an array of objects by multiple properties
- Build a data aggregation function for sales data