Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 6 – Arrays and array methods

Copying array methods: toSorted, toSpliced, with

Change JavaScript arrays without mutating them: toSorted, toReversed, toSpliced and with() return new arrays, while push, sort and splice change the original.

  • Beginner
  • 15 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Replace copy-then-mutate code with toSorted, toReversed, toSpliced and with
  • List the array methods that change the array they are called on
  • Update state with copying methods while unchanged items stay shared
  • Estimate what a copy costs and avoid one copy per item in a loop
  • Predict how the copying methods treat holes, subclasses and typed arrays

Before you start

What you’ll learn · Before you start

What you will learn

  • Replace copy-then-mutate code with toSorted, toReversed, toSpliced and with
  • List the array methods that change the array they are called on
  • Update state with copying methods while unchanged items stay shared
  • Estimate what a copy costs and avoid one copy per item in a loop
  • Predict how the copying methods treat holes, subclasses and typed arrays

Before you start

On this page

ECMAScript 2023 added four array methods that return a changed copy and leave the original array alone. toSorted() and toReversed() are the copying forms of sort() and reverse(). toSpliced(start, skipCount, ...items) removes and inserts items like splice(), but returns the new array instead of the removed items. with(index, value) returns a copy with one item replaced, the copying form of list[index] = value. Reach for them whenever other code still holds the array: state that a screen compares with its previous version, data that several functions share, or input that a function must not change. Nine methods change the array they are called on: push, pop, shift, unshift, splice, sort, reverse, fill and copyWithin. Every copy costs time and memory in proportion to the array’s length, so one copy per change is cheap, and one copy per item inside a loop is not.

The four copying methods

toSorted, toReversed, toSpliced and with JavaScript · four-methods.mjs
const queue = ['Ravi', 'Asha', 'Meena', 'Kabir'];

console.log(queue.toSorted()); // a sorted copy, like sort()
console.log(queue.toReversed()); // a reversed copy, like reverse()
console.log(queue.toSpliced(1, 2, 'Dev')); // remove 2 items at index 1, insert 'Dev'
console.log(queue.with(0, 'Zoya')); // replace the item at index 0
console.log(queue); // the original, untouched by all four

// splice() changes the array and returns what it removed; toSpliced() returns the new array.
const board = [...queue];
console.log(board.splice(1, 2, 'Dev'), board);

Output

[ 'Asha', 'Kabir', 'Meena', 'Ravi' ]
[ 'Kabir', 'Meena', 'Asha', 'Ravi' ]
[ 'Ravi', 'Dev', 'Kabir' ]
[ 'Zoya', 'Asha', 'Meena', 'Kabir' ]
[ 'Ravi', 'Asha', 'Meena', 'Kabir' ]
[ 'Asha', 'Meena' ] [ 'Ravi', 'Dev', 'Kabir' ]

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node four-methods.mjs

Each call returned a new array, and the last line but one shows queue exactly as it started:

  • toSorted(compare) takes the same comparator as sort(), with the same rules, and is just as stable. With no comparator it compares items as strings, as sort() does.
  • toReversed() returns the items in the opposite order.
  • toSpliced(start, skipCount, ...items) skips skipCount items from start and puts items in their place. toSpliced(1, 2, 'Dev') dropped Asha and Meena and inserted Dev.
  • with(index, value) replaces the item at one index.

The last line shows the difference that trips people up when they switch: splice() returns the items it removed and changes board, while toSpliced() returns the whole new array. Code that read the removed items from splice() must take them with slice(start, start + skipCount) before it changes to toSpliced().

with(): replace one item

Negative indexes and indexes outside the array JavaScript · with-index.mjs
const seats = ['A1', 'A2', 'A3'];

// A negative index counts from the end, as with at().
console.log(seats.with(-1, 'A3 window'));

// An index outside the array is an error, not a new slot.
try {
  seats.with(3, 'A4');
} catch (error) {
  console.log(error.name);
}

// Assigning with brackets on a copy grows the array instead, and leaves holes.
const copy = [...seats];
copy[5] = 'A6';
console.log(copy, copy.length);

Output

[ 'A1', 'A2', 'A3 window' ]
RangeError
[ 'A1', 'A2', 'A3', <2 empty items>, 'A6' ] 6

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node with-index.mjs

with() accepts a negative index, which counts back from the end as at() does, so with(-1, …) replaces the last item. An index outside the array throws a RangeError: with() replaces an item and never adds one. Assigning to an index past the end with brackets behaves differently: the array grows, and the indexes in between become holes. That RangeError is useful, because a wrong index fails at once instead of leaving a gap that shows up later.

Which methods change the array

The surest way to learn which methods change an array is to freeze one and let it refuse every change:

A frozen array against seventeen methods JavaScript · mutation-audit.mjs
// A frozen array refuses every change, so each method that tries to change it throws a TypeError.
const frozen = Object.freeze(['b', 'c', 'a']);

const methods = {
  push: (a) => a.push('d'),
  pop: (a) => a.pop(),
  shift: (a) => a.shift(),
  unshift: (a) => a.unshift('z'),
  splice: (a) => a.splice(0, 1),
  sort: (a) => a.sort(),
  reverse: (a) => a.reverse(),
  fill: (a) => a.fill('x'),
  copyWithin: (a) => a.copyWithin(0, 1),
  toSorted: (a) => a.toSorted(),
  toReversed: (a) => a.toReversed(),
  toSpliced: (a) => a.toSpliced(0, 1),
  with: (a) => a.with(0, 'z'),
  slice: (a) => a.slice(1),
  concat: (a) => a.concat(['d']),
  map: (a) => a.map((s) => s.toUpperCase()),
  filter: (a) => a.filter((s) => s !== 'c'),
};

for (const [name, call] of Object.entries(methods)) {
  try {
    console.log(`${name.padEnd(10)} returns ${JSON.stringify(call(frozen))}`);
  } catch (error) {
    console.log(`${name.padEnd(10)} throws a ${error.name}: it changes the array`);
  }
}
console.log('the frozen array is still', JSON.stringify(frozen));

Output

push       throws a TypeError: it changes the array
pop        throws a TypeError: it changes the array
shift      throws a TypeError: it changes the array
unshift    throws a TypeError: it changes the array
splice     throws a TypeError: it changes the array
sort       throws a TypeError: it changes the array
reverse    throws a TypeError: it changes the array
fill       throws a TypeError: it changes the array
copyWithin throws a TypeError: it changes the array
toSorted   returns ["a","b","c"]
toReversed returns ["a","c","b"]
toSpliced  returns ["c","a"]
with       returns ["z","c","a"]
slice      returns ["c","a"]
concat     returns ["b","c","a","d"]
map        returns ["B","C","A"]
filter     returns ["b","a"]
the frozen array is still ["b","c","a"]

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node mutation-audit.mjs

Object.freeze() makes every index and the length of an array read-only, so each method that writes to the array throws a TypeError. The nine methods that threw are the mutating ones. Everything else in the list built a new array and left the frozen one as it was. Each mutating method has a copying way to get the same result:

Changes the array Copying form Note
sort(compare) toSorted(compare) same comparator, same stable order
reverse() toReversed()
splice(start, count, ...items) toSpliced(start, count, ...items) splice returns the removed items, toSpliced the new array
list[i] = value with(i, value) with throws a RangeError outside the array
push(...items) [...list, ...items] or concat(items)
unshift(...items) [...items, ...list]
pop(), shift() slice(0, -1), slice(1) read the item first with at(-1) or at(0)
fill(value) Array.from(list, () => value)

Object.freeze() is also a cheap guard in tests: freeze the input, and any accidental change throws at the line that made it. It is shallow, though. A frozen array of objects still lets those objects change, so freeze the items as well when they matter, as the exercise below does.

Updating state without changing it

Copying methods suit state that is replaced as a whole on every update, as a reducer replaces it. Here a to-do list goes through three updates:

Tick an item, insert one, remove one JavaScript · state-update.mjs
// A to-do list kept as state: every update returns a new array and never changes the old one.
const todos = Object.freeze([
  Object.freeze({ text: 'Pay electricity bill', done: false }),
  Object.freeze({ text: 'Book train tickets', done: false }),
  Object.freeze({ text: 'Renew bus pass', done: false }),
]);

// Mark item 1 done: a new array whose item 1 is a new object.
const ticked = todos.with(1, { ...todos[1], done: true });

// Insert after item 0, and remove item 2.
const added = ticked.toSpliced(1, 0, { text: 'Call the bank', done: false });
const removed = added.toSpliced(3, 1);

for (const t of removed) console.log(t.done ? '[x]' : '[ ]', t.text);

// What was copied: the array always, an item only when it changed.
console.log('new array:', ticked !== todos);
console.log('item 0 is the same object:', ticked[0] === todos[0]);
console.log('item 1 is a new object:', ticked[1] !== todos[1]);
console.log('the old list still says:', todos.map((t) => t.done).join(', '));

Output

[ ] Pay electricity bill
[ ] Call the bank
[x] Book train tickets
new array: true
item 0 is the same object: true
item 1 is a new object: true
the old list still says: false, false, false

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node state-update.mjs

with(1, { ...todos[1], done: true }) builds a new array and a new object for the item that changed. It does not copy the other items: the new array points at the same objects as the old one.

with(1, changed) builds a new array that shares items 0 and 2 with the old one and holds one new item at index 1.todos[ item 0, item 1, item 2 ]todos.with(1, changed)changed = { ...item 1, done: true }a new array[ item 0, changed, item 2 ]item 0 and item 2the same objects in both arraystodos is unchangedit still holds item 1copies the listsharesand

What todos.with(1, changed) copies

Text description of the diagram

Five boxes, one above the other.

  1. The old array, todos, holds item 0, item 1 and item 2.
  2. todos.with(1, changed) is called, where changed is a new object made with { ...item 1, done: true }.
  3. It copies the list into a new array: item 0, changed, item 2.
  4. Item 0 and item 2 are not copied: the new array points at the same objects as the old one.
  5. todos is unchanged and still holds the original item 1, so code that kept the old array still sees the old list.

The copy is shallow: an array of n items costs n new slots, while the item objects themselves are shared.

That sharing is what makes copying affordable: an update copies the list of references, one slot per item, and creates new objects only for the items that changed. It also means that the copy is shallow. If code changes a shared item in place, both lists see the change, so a change to an item must also be made as a copy, with { ...item, done: true } or a similar new object.

What a copy costs

Each copying method reads every item of the array and writes a new array of about the same length. One copy for one update is cheap. One copy for each item, inside a loop, is not:

Doubling 10,000 numbers three ways JavaScript · copy-cost.mjs
// 10,000 numbers, each to be doubled.
const values = Array.from({ length: 10000 }, (_, i) => i);

function time(label, run) {
  const start = performance.now();
  const result = run();
  console.log(`${label}${(performance.now() - start).toFixed(1)} ms (last item ${result.at(-1)})`);
}

// with() copies the whole array for every single change: 10,000 copies of 10,000 items.
time('with() for each item:    ', () => {
  let current = values;
  for (let i = 0; i < current.length; i++) current = current.with(i, current[i] * 2);
  return current;
});

// map() builds one new array with every change in it.
time('one map():               ', () => values.map((n) => n * 2));

// Or copy once and change the copy, which nobody else can see yet.
time('one copy, then a loop:   ', () => {
  const copy = values.slice();
  for (let i = 0; i < copy.length; i++) copy[i] *= 2;
  return copy;
});

Output

with() for each item:    32.3 ms (last item 19998)
one map():               0.2 ms (last item 19998)
one copy, then a loop:   0.3 ms (last item 19998)

This output changes from run to run: the times depend on the computer and on what else it is running

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node copy-cost.mjs

Calling with() once per item makes 10,000 copies of a 10,000-item array: 100 million items written to change 10,000. On an Apple M5 Pro with Node.js 24.21.0 that took tens of milliseconds, against well under a millisecond for one map(), and the gap grows with the square of the length: with 50,000 items the same loop writes 2.5 billion items, and it took 21 seconds, while one map() took 9 milliseconds. When many items change at once, build the new array in one pass with map(), or copy once with slice() and change the copy, which is safe because no other code holds it yet.

Holes, subclasses, typed arrays and array-likes

Four edge cases of the copying methods JavaScript · edge-cases.mjs
// 1. Holes: the copy methods read every index, so a hole comes back as undefined.
const sparse = [1, , 3];
console.log(sparse.toReversed(), sparse.with(0, 9));

// 2. Subclasses: the copy methods always build a plain Array.
class Playlist extends Array {}
const songs = Playlist.from(['Night Train', 'Monsoon Song', 'Morning Raga']);
console.log(songs.toSorted() instanceof Playlist, songs.slice() instanceof Playlist);

// 3. Typed arrays have toSorted, toReversed and with, but no toSpliced: their length is fixed.
const levels = new Uint8Array([30, 10, 20]);
console.log(levels.toSorted(), typeof levels.toSpliced);
// with() converts the new value to the element type: 300 does not fit in a byte.
console.log(levels.with(0, 300));

// 4. Array-likes: the methods are generic, and the result is a real array.
const arrayLike = { length: 3, 0: 'c', 1: 'a', 2: 'b' };
console.log(Array.prototype.toSorted.call(arrayLike));

Output

[ 3, undefined, 1 ] [ 9, undefined, 3 ]
false true
Uint8Array(3) [ 10, 20, 30 ] undefined
Uint8Array(3) [ 44, 10, 20 ]
[ 'a', 'b', 'c' ]

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node edge-cases.mjs

Four details follow from how the standard defines these methods:

  • Holes become undefined. The copying methods read every index from 0 to the length, so a hole in the original comes back as undefined, and the new array has no holes. sort() and reverse() keep holes as holes.
  • The result is always a plain Array. A subclass such as Playlist gets a plain array back from toSorted(), while slice(), map() and filter() build an instance of the subclass.
  • Typed arrays have three of the four. Uint8Array and the other typed arrays have toSorted(), toReversed() and with(), but no toSpliced(), just as they have no splice(). with() converts the new value to the element type, so 300 stored in a byte becomes 44 (300 − 256).
  • They work on array-likes. Like most array methods, the copying methods only need a length and indexes, so Array.prototype.toSorted.call(arrayLike) returns a real, sorted array.

Where they run

The four methods are Baseline Widely available: Chrome and Edge 110, Firefox 115 and Safari 16 support them, and so do Node.js 20 and later. The Run buttons of this track use QuickJS, which has them too. Code that must still support older engines can fall back on copying first: [...list].sort(compare) for toSorted, [...list].reverse() for toReversed, and a slice() copy changed with splice() or an index assignment for the other two.

JSON Diff & Compare Paste the old and the new state as JSON to see exactly which items an update changed. JavaScript & TypeScript Online Compiler Freeze an array of your own and try methods on it, to see which ones throw.

Exercise: a to-do reducer that copies

Exercise · Medium · JavaScript

Rewrite a to-do reducer with the copying methods

todos(state, action) takes a list of to-dos such as [{ text: 'Pay rent', done: false }] and an action, and returns the next list. The starter code works, but it changes the list it is given, so a screen that compares the old and the new list never sees a change. Rewrite it so that it **never changes state or any item in it**:

  • { type: 'add', text } adds { text, done: false } at the end;
  • { type: 'toggle', index } flips done of the item at index (a negative index counts from the end, and an index outside the list throws a RangeError);
  • { type: 'remove', index } removes the item at index;
  • { type: 'move', from, to } takes the item at from out and puts it back so that it ends up at index to;
  • { type: 'doneLast' } puts the items that are done after the ones that are not, keeping the order inside each group;
  • any other action returns state itself.

Items that an action does not change must stay the very same objects in the new list. The tests freeze state and its items, so any change to them throws a TypeError.

const list = [{ text: 'Pay rent', done: false }, { text: 'Buy milk', done: true }];
todos(list, { type: 'toggle', index: 0 })
// [{ text: 'Pay rent', done: true }, { text: 'Buy milk', done: true }], and list is unchanged

Starter code · todos.mjs

/**
 * The next to-do list after `action`. Works, but changes `state`: rewrite it with the copying methods so that
 * `state` and its items are never changed.
 */
export function todos(state, action) {
  switch (action.type) {
    case 'add':
      state.push({ text: action.text, done: false });
      return state;
    case 'toggle': {
      const index = action.index < 0 ? state.length + action.index : action.index;
      if (index < 0 || index >= state.length) throw new RangeError(`no item at index ${action.index}`);
      state[index].done = !state[index].done;
      return state;
    }
    case 'remove':
      state.splice(action.index, 1);
      return state;
    case 'move': {
      const [item] = state.splice(action.from, 1);
      state.splice(action.to, 0, item);
      return state;
    }
    case 'doneLast':
      return state.sort((a, b) => Number(a.done) - Number(b.done));
    default:
      return state;
  }
}
The sample tests · todos.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { todos } from './todos.mjs';

const item = (text, done = false) => Object.freeze({ text, done });
const list = (...items) => Object.freeze(items);
const texts = (state) => state.map((t) => (t.done ? `${t.text} (done)` : t.text));

test('add puts a new item at the end and leaves state alone', () => {
  const state = list(item('Pay rent'));
  const next = todos(state, { type: 'add', text: 'Buy milk' });
  assert.deepEqual(texts(next), ['Pay rent', 'Buy milk']);
  assert.equal(next[1].done, false);
  assert.equal(state.length, 1);
  assert.equal(next[0], state[0], 'the old item is the same object');
});

test('toggle flips one item and keeps the others as they are', () => {
  const state = list(item('Pay rent'), item('Buy milk', true), item('Call home'));
  const next = todos(state, { type: 'toggle', index: 1 });
  assert.deepEqual(texts(next), ['Pay rent', 'Buy milk', 'Call home']);
  assert.notEqual(next, state);
  assert.notEqual(next[1], state[1], 'the toggled item is a new object');
  assert.equal(next[0], state[0]);
  assert.equal(next[2], state[2]);
  assert.equal(state[1].done, true, 'the old item still says done');
  assert.deepEqual(texts(todos(next, { type: 'toggle', index: -1 })), ['Pay rent', 'Buy milk', 'Call home (done)']);
});

test('toggle outside the list throws a RangeError', () => {
  const state = list(item('Pay rent'));
  assert.throws(() => todos(state, { type: 'toggle', index: 1 }), RangeError);
  assert.throws(() => todos(state, { type: 'toggle', index: -2 }), RangeError);
});

test('remove takes out one item', () => {
  const state = list(item('A'), item('B'), item('C'));
  assert.deepEqual(texts(todos(state, { type: 'remove', index: 1 })), ['A', 'C']);
  assert.deepEqual(texts(todos(state, { type: 'remove', index: 0 })), ['B', 'C']);
  assert.deepEqual(texts(state), ['A', 'B', 'C']);
});

test('move puts an item at its new index', () => {
  const state = list(item('A'), item('B'), item('C'), item('D'));
  assert.deepEqual(texts(todos(state, { type: 'move', from: 0, to: 2 })), ['B', 'C', 'A', 'D']);
  assert.deepEqual(texts(todos(state, { type: 'move', from: 3, to: 0 })), ['D', 'A', 'B', 'C']);
  const moved = todos(state, { type: 'move', from: 1, to: 3 });
  assert.deepEqual(texts(moved), ['A', 'C', 'D', 'B']);
  assert.equal(moved[3], state[1], 'a move keeps the same item object');
});

test('doneLast keeps the order inside each group', () => {
  const state = list(item('A', true), item('B'), item('C', true), item('D'));
  assert.deepEqual(texts(todos(state, { type: 'doneLast' })), ['B', 'D', 'A (done)', 'C (done)']);
  assert.deepEqual(texts(state), ['A (done)', 'B', 'C (done)', 'D']);
});

test('an unknown action returns state itself', () => {
  const state = list(item('A'));
  assert.equal(todos(state, { type: 'rename' }), state);
});
A hint

Use one copying method per action: toSpliced to add, remove or move (a move is a removal followed by an insertion), with to put a changed copy of one item in place, built with { ...item, done: !item.done }, and toSorted for doneLast. with already throws a RangeError for an index outside the list.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 Which of these calls change the array list itself? Choose every one.

    Choose every answer that is right.

    Show the answer to question 1

    Answer:

    • list.splice(0, 1)
    • list.reverse()
    • list.fill('')

    reverse, fill and splice write to the array they are called on; on a frozen array each of them throws a TypeError. toReversed, slice and with build a new array and leave list as it was.

  2. Question 2 of 6 What does this code print?

    Read the code, then choose one answer.

    const list = [1, 2, 3];
    const next = list.toSpliced(1, 1);
    console.log(list.length, next);
    Show the answer to question 2

    Answer: 3 [ 1, 3 ]

    toSpliced leaves list alone, so it still has 3 items, and returns the new array without the item at index 1. splice(1, 1) would have returned [ 2 ] and left list as [ 1, 3 ].

  3. Question 3 of 6 What does with-index.mjs print?

    What does this program print? Choose one answer.

    const seats = ['A1', 'A2', 'A3'];
    
    // A negative index counts from the end, as with at().
    console.log(seats.with(-1, 'A3 window'));
    
    // An index outside the array is an error, not a new slot.
    try {
      seats.with(3, 'A4');
    } catch (error) {
      console.log(error.name);
    }
    
    // Assigning with brackets on a copy grows the array instead, and leaves holes.
    const copy = [...seats];
    copy[5] = 'A6';
    console.log(copy, copy.length);
    Show the answer to question 3

    Answer: it prints

    [ 'A1', 'A2', 'A3 window' ]
    RangeError
    [ 'A1', 'A2', 'A3', <2 empty items>, 'A6' ] 6

    with(-1, …) replaces the last item. with(3, …) is outside a three-item array, so it throws a RangeError instead of adding an item. Assigning to index 5 of a copy with brackets grows the copy to six items, with two holes in between.

  4. Question 4 of 6 const sorted = Object.freeze([3, 1, 2]).toSorted(); What is sorted?

    Choose one answer.

    Show the answer to question 4

    Answer: A new array [1, 2, 3], which is not frozen

    toSorted only reads the original, so freezing it does not matter, and it builds an ordinary new array. Only the methods that write to the array, such as sort, throw on a frozen one.

  5. Question 5 of 6 class Playlist extends Array {}. What does Playlist.from(songs).toSorted() return?

    Choose one answer.

    Show the answer to question 5

    Answer: A plain Array

    The copying methods always create a plain array. slice(), map() and filter() are different: they build an instance of the subclass.

  6. Question 6 of 6 A loop calls items = items.with(i, fix(items[i])) for every index of a 50,000-item array. About how many items does it write in total?

    Choose one answer.

    Show the answer to question 6

    Answer: About 2.5 billion

    Each with() call copies the whole array, 50,000 items, and the loop calls it 50,000 times: 50,000 × 50,000 is 2.5 billion. One map() writes the 50,000 items once.

Interview questions

What is the difference between splice() and toSpliced()? Both remove a number of items from a start index and can insert new items in their place, with the same arguments: start, how many to remove, then the items to insert. splice() changes the array it is called on and returns an array of the items it removed. toSpliced(), added in ECMAScript 2023, leaves the original alone and returns a new array with the removal and insertion applied, reading any holes as undefined. So list.splice(1, 1) gives you the removed item and a shorter list, while list.toSpliced(1, 1) gives you a shorter copy and an unchanged list. Use toSpliced() for shared data and state that must not change; use splice() on an array that only your code holds, when the removed items are what you need.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    An import screen applies one fix per row with rows = rows.with(i, fixRow(rows[i])) in a loop, to keep state immutable. With 50,000 rows the tab freezes for seconds, and a teammate concludes that the copying methods are too slow to use. Explain the cost with numbers, rewrite the update, and say when with() is the right tool.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A settings page shows the user's saved addresses sorted with addresses.toSorted(byCity). When the user edits an address in the sorted view with sorted[i].city = 'Pune', the saved list changes too, and the undo button stops working. Explain why, and fix the edit so that the saved list stays as it was.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • toSorted(), toReversed(), toSpliced() and with() return a new array and never change the original.
  • push, pop, shift, unshift, splice, sort, reverse, fill and copyWithin change the array they are called on; a frozen array makes each of them throw a TypeError.
  • splice() returns the removed items; toSpliced() returns the new array.
  • with() accepts negative indexes and throws a RangeError outside the array, instead of growing it.
  • The copies are shallow: unchanged items are shared, so change an item by replacing it with a new object.
  • Each copy costs time in proportion to the length: use one map() or one private copy for many changes, never one with() per item.
  • Holes come back as undefined, subclasses get a plain Array, and typed arrays have every method but toSpliced().

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.