Suggest an editImprove this articleRefine the answer for “Mutating and non-mutating Array methods in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Mutating array methods** change the original array. Non-mutating methods return new data without touching the original. ```js const arr = [3, 1, 2]; arr.sort(); // mutates arr - [1, 2, 3] const copy = [...arr].sort(); // safe - arr unchanged ``` **Key:** In React and Redux, always use non-mutating methods (`map`, `filter`, `slice`, spread) - mutation bypasses React's change detection.Shown above the full answer for quick recall.Answer (EN)Image**Mutating array methods** change the original array in place. Non-mutating methods return new data without touching the original. ## Theory ### TL;DR - Mutating: `push()`, `pop()`, `splice()`, `sort()`, `reverse()` - the array changes after the call - Non-mutating: `map()`, `filter()`, `slice()`, `concat()`, `find()` - original stays the same - Analogy: mutating is editing a document directly; non-mutating is making a copy first and editing that - React and Redux require non-mutating methods - mutation bypasses change detection entirely - Quick fix for sort: `[...arr].sort()` gives you a sorted copy without changing `arr` ### Quick example ```js const original = [3, 1, 2]; // sort() mutates - original changes original.sort(); console.log(original); // [1, 2, 3] - changed // spread + sort - original stays safe const nums = [3, 1, 2]; const sorted = [...nums].sort(); console.log(nums); // [3, 1, 2] - unchanged console.log(sorted); // [1, 2, 3] - new array ``` `sort()` returns a reference to the same array it modified, not a new one. That catches a lot of developers who expect it to behave like `map()`. ### Key difference Mutating methods modify the array's memory directly. Non-mutating methods allocate new memory, compute values, and return the result. If two variables point to the same array, one mutation affects both. React's change detection compares references (`arr !== prevArr`), so mutating an array makes React think nothing changed and skip the re-render. ### When to use - **Mutating methods:** when you own the array and no other code holds a reference to it; in tight loops where allocating new arrays is measurably slow - **Non-mutating methods:** default choice in React state updates, Redux reducers, and any function that receives an array as a parameter - **Mixed approach:** mutate inside a function that owns the data, then return the result as a new value to callers ### Comparison table | Method | Type | Returns | Modifies original | Use case | |--------|------|---------|------------------|----------| | `push()` | Mutating | New length | Yes | Adding to an array you own | | `concat()` | Non-mutating | New array | No | Combining arrays safely | | `splice()` | Mutating | Removed elements | Yes | Removing/inserting at index | | `slice()` | Non-mutating | New array | No | Getting a portion safely | | `sort()` | Mutating | Same array (sorted) | Yes | Sorting when mutation is acceptable | | `toSorted()` | Non-mutating | New array | No | Sorting without side effects (ES2023) | | `reverse()` | Mutating | Same array (reversed) | Yes | Reversing when mutation is acceptable | | `toReversed()` | Non-mutating | New array | No | Reversing without side effects (ES2023) | | `map()` | Non-mutating | New array | No | Transforming data (React, Redux) | | `filter()` | Non-mutating | New array | No | Filtering data (React, Redux) | | `find()` | Non-mutating | Single element | No | Searching without changes | | `fill()` | Mutating | Same array | Yes | Initializing array values | ### Common mistakes **Mistake 1: Mutating state in React** ```js // Wrong - mutation, React skips the re-render const handleAdd = () => { state.items.push(newItem); setState(state); // same reference - React sees no change }; // Right - new array, React detects the change const handleAdd = () => { setState([...state.items, newItem]); }; ``` React compares `prevState === newState`. Mutation keeps the same reference, so the check passes and the component stays stale. Always create a new array in state updates. **Mistake 2: Expecting splice() to return the modified array** ```js const arr = [1, 2, 3]; const result = arr.splice(1, 1); console.log(result); // [2] - the REMOVED element, not [1, 3] console.log(arr); // [1, 3] - the modified array, not in result ``` The `splice()` return value catches almost everyone the first time. You expect the resulting array, you get the removed elements instead. Use `arr` directly after calling splice, or switch to `filter()` for non-mutating behavior. **Mistake 3: Sorting numbers without a comparator** ```js const numbers = [10, 5, 40, 25]; numbers.sort(); console.log(numbers); // [10, 25, 40, 5] - wrong, sorted as strings numbers.sort((a, b) => a - b); console.log(numbers); // [5, 10, 25, 40] - correct ``` Without a comparator, `sort()` converts elements to strings and compares character codes. "40" comes before "5" alphabetically. For numbers, always pass `(a, b) => a - b`. **Mistake 4: Mutating an array while iterating it** ```js // Wrong - skips elements unpredictably const arr = [1, 2, 3, 4]; arr.forEach(item => { if (item === 2) arr.splice(arr.indexOf(item), 1); }); // Right - filter creates a new array const filtered = [1, 2, 3, 4].filter(item => item !== 2); console.log(filtered); // [1, 3, 4] ``` Splicing during iteration shifts indices and causes elements to be skipped. Use `filter()` instead. ### Real-world usage - **React:** `map()`, `filter()`, `concat()`, spread operator for all state updates - **Redux:** reducers must return new state - `filter()`, `map()`, spread syntax; never `push()` or `splice()` - **Vue.js:** non-mutating methods keep the reactive system working correctly - **Node.js/Express:** mutating methods are fine inside route handlers that own and build the response data - **ES2023:** `toSorted()`, `toReversed()`, `toSpliced()` are the built-in non-mutating alternatives - no need to spread before sorting ### Follow-up questions **Q:** Why does React require non-mutating array methods? **A:** React uses reference equality (`prevState === newState`) to detect changes. Mutating an array keeps the same reference, so the check returns `true` and React skips the re-render. A new array always has a different reference. **Q:** What is the performance cost of non-mutating methods? **A:** Non-mutating methods allocate new memory and copy data. For arrays under 1000 elements the difference is negligible. In tight loops with large arrays, mutating can matter. For most application code, immutability is worth the small overhead. **Q:** How do I make sort() non-mutating without ES2023? **A:** Create a copy first: `[...arr].sort()` or `arr.slice().sort()`. Both produce a shallow copy, so sorting the copy leaves the original untouched. **Q:** (Senior) Why does `map()` always return a new array, but `sort()` modifies the original? **A:** `sort()` rearranges elements in place and reuses the existing memory allocation - a performance-conscious decision from the original spec. `map()` produces transformed values and there is no way to do that without new memory, because the values themselves change. The split comes from algorithmic necessity as much as design. ## Examples ### React state update with sort ```js function TodoList({ todos, setTodos }) { // Wrong - mutates todos, React won't re-render const handleSortWrong = () => { todos.sort((a, b) => a.priority - b.priority); setTodos(todos); // same reference, no re-render }; // Right - new array, React detects the change const handleSort = () => { const sorted = [...todos].sort((a, b) => a.priority - b.priority); setTodos(sorted); }; return <ul>{todos.map(t => <li key={t.id}>{t.text}</li>)}</ul>; } ``` `[...todos]` creates a shallow copy of the array. Sorting that copy produces a new reference, which React sees as a change and re-renders the component. ### splice() vs slice() - same name, opposite behavior ```js const arr = [1, 2, 3, 4, 5]; // splice() - mutates, returns removed elements const removed = arr.splice(1, 2); console.log(removed); // [2, 3] - what was removed console.log(arr); // [1, 4, 5] - what remains (arr is changed) // slice() - no mutation, returns what you keep const arr2 = [1, 2, 3, 4, 5]; const kept = arr2.slice(1, 3); console.log(kept); // [2, 3] - what you kept console.log(arr2); // [1, 2, 3, 4, 5] - unchanged ``` `splice()` and `slice()` look similar but work oppositely. `splice()` mutates and returns what was removed. `slice()` returns what you selected and leaves the original alone. ### Numeric sort gotcha ```js const prices = [100, 25, 1000, 50]; // Default sort treats numbers as strings prices.sort(); console.log(prices); // [100, 1000, 25, 50] - alphabetical, not numeric // Non-mutating sort with a numeric comparator const sortedPrices = [...prices].sort((a, b) => a - b); console.log(sortedPrices); // [25, 50, 100, 1000] - correct console.log(prices); // [100, 1000, 25, 50] - original unchanged ``` Default `sort()` uses string comparison. "1000" comes before "25" alphabetically. For numeric sorting, always provide a comparator. The spread + sort pattern keeps the original intact.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.