Arrays - Working with Lists
TL;DR — Quick Summary
- Prefer non-mutating methods (
map,filter,slice, spread) — avoid mutating shared arrays unexpectedly. - Always use a comparator with
sort():(a, b) => a - b— the default lexicographic sort produces wrong results for numbers. - Use
arr.at(-1)to access the last element — much cleaner thanarr[arr.length - 1]. - Destructuring and spread are fundamental in modern JavaScript — master them for cleaner, more expressive code.
Lesson Overview
Arrays are ordered, indexed lists — the most fundamental data structure in programming. Almost every real application involves arrays: a list of users, a shopping cart, search results, messages, notifications. Mastering array manipulation is non-negotiable.
JavaScript arrays are dynamic (grow/shrink at runtime), zero-indexed, and can hold mixed types (though you should avoid this in practice). They provide a rich set of built-in methods, many of which were covered in Loops — here we go deeper into mutation vs. immutability, destructuring, spreading, and advanced patterns used in real codebases.
Conceptual Deep Dive
Creation and access: Arrays are created with [] or Array.from(). Elements are accessed by index starting at 0. The last element is at arr[arr.length - 1] or arr.at(-1).
Mutating methods (modify the original array):
push/pop: add/remove from endunshift/shift: add/remove from startsplice(index, deleteCount, ...items): insert/remove at any positionsort: sort in place (use a comparator function!)reverse: reverse in placefill: fill with a value
Non-mutating methods (return new arrays — prefer these):
slice(start, end): copy a portionconcat: join arraysmap,filter,flat,flatMap: transform[...arr]orArray.from(arr): shallow copy
Destructuring and spread/rest are modern syntax for clean array handling.
Implementation Lab
// Creation
const fruits = ['apple', 'banana', 'cherry'];
const zeros = new Array(5).fill(0); // [0, 0, 0, 0, 0]0, 0, 0, 0, 0]
const range = Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]1, 2, 3, 4, 5]
// Access
console.log(fruits[0]); // 'apple'
console.log(fruits.at(-1)); // 'cherry' (last element)(last element)
console.log(fruits.length); // 3
// ✅ Mutating methods
const cart = ['laptop'];
cart.push('phone', 'tablet'); // add to end → ['laptop', 'phone', 'tablet']'laptop', 'phone', 'tablet']
cart.pop(); // remove from end → ['laptop', 'phone']'laptop', 'phone']
cart.unshift('charger'); // add to start → ['charger', 'laptop', 'phone']'charger', 'laptop', 'phone']
cart.shift(); // remove from start → ['laptop', 'phone']'laptop', 'phone']
// splice: insert/remove at any index
const items = ['a', 'b', 'c', 'd'];
const removed = items.splice(1, 2, 'x', 'y'); // at index 1, remove 2, insert x, y2, insert x, y
console.log(items); // ['a', 'x', 'y', 'd']'a', 'x', 'y', 'd']
console.log(removed); // ['b', 'c']'b', 'c']
// slice: copy without modifying original
const letters = ['a', 'b', 'c', 'd', 'e'];
console.log(letters.slice(1, 3)); // ['b', 'c'] (index 1 to 3, not including 3)'b', 'c'] (index 1 to 3, not including 3)
console.log(letters.slice(-2)); // ['d', 'e'] (last 2)'d', 'e'] (last 2)
console.log(letters); // unchanged!
// Searching
console.log(fruits.includes('banana')); // true
console.log(fruits.indexOf('cherry')); // 2 (index, or -1 if not found)(index, or -1 if not found)
console.log(fruits.find(f => f.startsWith('a'))); // 'apple'// ⚠️ sort() without comparator: converts to string first (bug!)) without comparator: converts to string first (bug!)
const nums = [10, 1, 21, 2];
console.log(nums.sort()); // [1, 10, 2, 21] ❌ lexicographic1, 10, 2, 21] ❌ lexicographic
console.log(nums.sort((a, b) => a - b)); // [1, 2, 10, 21] ✅ numeric1, 2, 10, 21] ✅ numeric
// Sort objects by property
const users = [
{ name: 'Charlie', age: 30 },
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 35 },
];
const byAge = [...users].sort((a, b) => a.age - b.age); // copy first!
const byName = [...users].sort((a, b) => a.name.localeCompare(b.name));
// Spread operator: shallow copy and merge
const original = [1, 2, 3];
const copy = [...original]; // shallow copy
const merged = [...original, 4, 5]; // extend
const combined = [...[1,2], ...[3,4], ...[5]]; // [1,2,3,4,5]1,2,3,4,5]
// Destructuring: unpack array values into variables
const [first, second, ...rest] = [10, 20, 30, 40, 50];
console.log(first); // 10
console.log(second); // 20
console.log(rest); // [30, 40, 50]30, 40, 50]
// Skip elements with empty slots
const [,, third] = ['a', 'b', 'c'];
console.log(third); // 'c'
// Swap variables without a temp
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2, 11Pro Tips — Senior Dev Insights
Array.from({ length: n }, (_, i) => ...) is the cleanest way to generate arrays — use it to create ranges, matrices, and mock data.
When you need to both filter and transform, flatMap is often cleaner than filter(...).map(...) for cases where map might return empty results.
In React and state management, never mutate arrays directly — always return new arrays: setState([...state, newItem]) not state.push(newItem).
Common Developer Pitfalls
sort() without a comparator on numbers — [10, 2, 1].sort() returns [1, 10, 2] because it sorts lexicographically (as strings).sort, splice, reverse, push modify the original. Use [...arr].sort() to sort a copy.splice and slice — splice mutates and removes elements; slice returns a portion without mutating.indexOf to find an object in an array — it compares by reference, so the same-looking object won't match unless it's the exact same reference. Use find() with a condition.Interview Mastery
slice(start, end) returns a copy of a portion of the array without modifying the original. splice(index, deleteCount, ...newItems) modifies the original array by removing and/or inserting elements, and returns the removed elements. Memory trick: 'splice' mutates (both have five letters and are mutating), 'slice' is like slicing off a piece without damaging the whole. In practice, prefer slice and spread over splice for immutable patterns.
Use sort() with a comparator: arr.sort((a, b) => a.property - b.property) for numbers, or a.property.localeCompare(b.property) for strings. Always sort a copy to avoid mutating the original: [...arr].sort(...). For multiple sort keys: sort by first key, return 0 to fall through to the next comparator: (a, b) => a.age - b.age || a.name.localeCompare(b.name).
Destructuring unpacks array values into named variables: const [first, second, ...rest] = array. Useful for: function return values (const [data, error] = fetchData()), swapping variables ([a, b] = [b, a]), ignoring elements (const [,,third] = arr), working with React useState (const [count, setCount] = useState(0)). It's cleaner than indexed access and documents intent — the variable name describes what the value represents.
Hands-on Lab Exercises
addTask, removeTask, completeTask, getByPriority functions — use only immutable array operations.sortBy(arr, key, direction) function that sorts an array of objects by any property in ascending or descending order.groupBy(arr, key) using reduce — it should return an object grouping array items by a property value.flat(Infinity) and a Set.Real-World Practice Scenarios
map and destructuring.Deepen Your Knowledge
Arrays - Working with Lists
TL;DR — Quick Summary
- Prefer non-mutating methods (
map,filter,slice, spread) — avoid mutating shared arrays unexpectedly. - Always use a comparator with
sort():(a, b) => a - b— the default lexicographic sort produces wrong results for numbers. - Use
arr.at(-1)to access the last element — much cleaner thanarr[arr.length - 1]. - Destructuring and spread are fundamental in modern JavaScript — master them for cleaner, more expressive code.
Overview
Arrays are ordered, indexed lists — the most fundamental data structure in programming. Almost every real application involves arrays: a list of users, a shopping cart, search results, messages, notifications. Mastering array manipulation is non-negotiable.
JavaScript arrays are dynamic (grow/shrink at runtime), zero-indexed, and can hold mixed types (though you should avoid this in practice). They provide a rich set of built-in methods, many of which were covered in Loops — here we go deeper into mutation vs. immutability, destructuring, spreading, and advanced patterns used in real codebases.
Deep Dive Analysis
<p><strong>Creation and access:</strong> Arrays are created with <code>[]</code> or <code>Array.from()</code>. Elements are accessed by index starting at 0. The last element is at <code>arr[arr.length - 1]</code> or <code>arr.at(-1)</code>.</p><p><strong>Mutating methods</strong> (modify the original array):</p><ul><li><code>push</code> / <code>pop</code>: add/remove from end</li><li><code>unshift</code> / <code>shift</code>: add/remove from start</li><li><code>splice(index, deleteCount, ...items)</code>: insert/remove at any position</li><li><code>sort</code>: sort in place (use a comparator function!)</li><li><code>reverse</code>: reverse in place</li><li><code>fill</code>: fill with a value</li></ul><p><strong>Non-mutating methods</strong> (return new arrays — prefer these):</p><ul><li><code>slice(start, end)</code>: copy a portion</li><li><code>concat</code>: join arrays</li><li><code>map</code>, <code>filter</code>, <code>flat</code>, <code>flatMap</code>: transform</li><li><code>[...arr]</code> or <code>Array.from(arr)</code>: shallow copy</li></ul><p><strong>Destructuring</strong> and <strong>spread/rest</strong> are modern syntax for clean array handling.</p>
Implementation Reference
// Creation
const fruits = ['apple', 'banana', 'cherry'];
const zeros = new Array(5).fill(0); // [0, 0, 0, 0, 0]
const range = Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]
// Access
console.log(fruits[0]); // 'apple'
console.log(fruits.at(-1)); // 'cherry' (last element)
console.log(fruits.length); // 3
// ✅ Mutating methods
const cart = ['laptop'];
cart.push('phone', 'tablet'); // add to end → ['laptop', 'phone', 'tablet']
cart.pop(); // remove from end → ['laptop', 'phone']
cart.unshift('charger'); // add to start → ['charger', 'laptop', 'phone']
cart.shift(); // remove from start → ['laptop', 'phone']
// splice: insert/remove at any index
const items = ['a', 'b', 'c', 'd'];
const removed = items.splice(1, 2, 'x', 'y'); // at index 1, remove 2, insert x, y
console.log(items); // ['a', 'x', 'y', 'd']
console.log(removed); // ['b', 'c']
// slice: copy without modifying original
const letters = ['a', 'b', 'c', 'd', 'e'];
console.log(letters.slice(1, 3)); // ['b', 'c'] (index 1 to 3, not including 3)
console.log(letters.slice(-2)); // ['d', 'e'] (last 2)
console.log(letters); // unchanged!
// Searching
console.log(fruits.includes('banana')); // true
console.log(fruits.indexOf('cherry')); // 2 (index, or -1 if not found)
console.log(fruits.find(f => f.startsWith('a'))); // 'apple'// ⚠️ sort() without comparator: converts to string first (bug!)
const nums = [10, 1, 21, 2];
console.log(nums.sort()); // [1, 10, 2, 21] ❌ lexicographic
console.log(nums.sort((a, b) => a - b)); // [1, 2, 10, 21] ✅ numeric
// Sort objects by property
const users = [
{ name: 'Charlie', age: 30 },
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 35 },
];
const byAge = [...users].sort((a, b) => a.age - b.age); // copy first!
const byName = [...users].sort((a, b) => a.name.localeCompare(b.name));
// Spread operator: shallow copy and merge
const original = [1, 2, 3];
const copy = [...original]; // shallow copy
const merged = [...original, 4, 5]; // extend
const combined = [...[1,2], ...[3,4], ...[5]]; // [1,2,3,4,5]
// Destructuring: unpack array values into variables
const [first, second, ...rest] = [10, 20, 30, 40, 50];
console.log(first); // 10
console.log(second); // 20
console.log(rest); // [30, 40, 50]
// Skip elements with empty slots
const [,, third] = ['a', 'b', 'c'];
console.log(third); // 'c'
// Swap variables without a temp
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2, 1Common Pitfalls
- •Calling <code>sort()</code> without a comparator on numbers — <code>[10, 2, 1].sort()</code> returns <code>[1, 10, 2]</code> because it sorts lexicographically (as strings).
- •Mutating arrays you didn't intend to — methods like <code>sort</code>, <code>splice</code>, <code>reverse</code>, <code>push</code> modify the original. Use <code>[...arr].sort()</code> to sort a copy.
- •Confusing <code>splice</code> and <code>slice</code> — <code>splice</code> mutates and removes elements; <code>slice</code> returns a portion without mutating.
- •Using <code>indexOf</code> to find an object in an array — it compares by reference, so the same-looking object won't match unless it's the exact same reference. Use <code>find()</code> with a condition.
Hands-on Practice
- ✓Build a task manager: implement <code>addTask</code>, <code>removeTask</code>, <code>completeTask</code>, <code>getByPriority</code> functions — use only immutable array operations.
- ✓Write a <code>sortBy(arr, key, direction)</code> function that sorts an array of objects by any property in ascending or descending order.
- ✓Implement <code>groupBy(arr, key)</code> using <code>reduce</code> — it should return an object grouping array items by a property value.
- ✓Flatten a nested array of arbitrary depth and deduplicate the result using <code>flat(Infinity)</code> and a <code>Set</code>.
Expert Pro Tips
Interview Preparation
Q: What's the difference between splice and slice?
Master Answer:
slice(start, end) returns a copy of a portion of the array without modifying the original. splice(index, deleteCount, ...newItems) modifies the original array by removing and/or inserting elements, and returns the removed elements. Memory trick: 'splice' mutates (both have five letters and are mutating), 'slice' is like slicing off a piece without damaging the whole. In practice, prefer slice and spread over splice for immutable patterns.
Q: How do you sort an array of objects by a property?
Master Answer:
Use sort() with a comparator: arr.sort((a, b) => a.property - b.property) for numbers, or a.property.localeCompare(b.property) for strings. Always sort a copy to avoid mutating the original: [...arr].sort(...). For multiple sort keys: sort by first key, return 0 to fall through to the next comparator: (a, b) => a.age - b.age || a.name.localeCompare(b.name).
Q: What is array destructuring and when is it useful?
Master Answer:
Destructuring unpacks array values into named variables: const [first, second, ...rest] = array. Useful for: function return values (const [data, error] = fetchData()), swapping variables ([a, b] = [b, a]), ignoring elements (const [,,third] = arr), working with React useState (const [count, setCount] = useState(0)). It's cleaner than indexed access and documents intent — the variable name describes what the value represents.
Simulated Scenarios
Extended Reading
MDN: Array reference
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
JavaScript.info: Arrays
https://javascript.info/array
JavaScript.info: Array methods
https://javascript.info/array-methods
© 2026 DevHub Engineering • All Proprietary Rights Reserved
Generated on March 7, 2026 • Ver: 4.0.2
Document Class: Master Education
Confidential Information • Licensed to User