Slicing, splicing & joining
Copy parts of an array, change arrays in place, join values into strings, split strings back into arrays, and avoid the classic fill and shallow-copy traps.
- 01Tell copying tools from changing toolsSay whether
slice,splice,concat,fill, andcopyWithinchange the original array, and what each one gives back. - 02Turn arrays into text and backUse
joinandsplitwith a separator, with no separator, with empty pieces, and with a limit. - 03Build new arrays on purposeUse spread,
Array.from, andArray.of, and avoid the empty slots ofnew Array(3)and the shared object offill({}).
Copy part of a list, change a list, or turn it into text
An array is an ordered list of values. In Array basics you learned to read an item by its index, check the length, and add or remove items. This lesson shows more array tools. Each tool does one of three jobs:
- It copies part of an array into a new array.
- It changes the array itself.
- It turns an array into text, or text into an array.
Two words come up again and again. A copy is a new array, and the original array stays the same. To mutate an array means to change the array itself. Every part of your code that uses that array then sees the change. A method that mutates is said to work in place.
const cart = ["tea", "bread", "apples"];
const preview = cart.slice(0, 2);
console.log(preview.join(","));
console.log(cart.join(","));Line 1 makes an array called cart with three items. Line 2 copies the first two items into a new array called preview. Line 3 prints tea,bread. Line 4 prints tea,bread,apples, so the cart still has all three items. The join(",") on lines 3 and 4 turns an array into text with commas between the items. You'll learn it properly later in this lesson.
For every method in this lesson, ask two questions. What does it give back? And does it change the original array?
The table below lists every tool in this lesson. Read each row as: "this tool changes the original array or not, it gives back this, and here is one thing to watch for."
| Tool | Does it change the original? | What does it give back? | Watch out for |
|---|---|---|---|
slice(start, end) | No. | A shallow copy of some of the items. | Objects inside are shared with the original. |
splice(start, deleteCount, ...items) | Yes. | An array of the removed items. | Other code that uses the same array sees the change. |
concat(...) | No. | A new shallow array. | An array you pass in gets opened one level deep. |
join(separator) | No. | A string. | null and undefined become empty text. |
split(separator, limit) | No. It works on a string, and strings never change. | A new array of strings. | Empty pieces are kept. |
fill(value, start, end) | Yes. | The same array. | An object value is shared by every filled slot. |
copyWithin(target, start, end) | Yes. | The same array. | It copies items inside the same array. |
Array.from(value, mapFn) | No. | A new array. | Its function can make a new object for each slot. |
Spread, slice, and concat make a new outer array. That kind of copy is called shallow: only the outer array is new. If an item is an object or another array, the copy and the original share that same item. You saw why in Objects & references.
This lesson also uses ideas from Strings and Default & rest parameters. A separator is the text placed between items, like a comma. Spread, written ..., opens an array and places its items one by one somewhere else.
slice copies; splice changes the array
INTERACTIVEslice and splice differ by only one letter, so they are easy to mix up. Picture a book. With slice, you photocopy some pages, and the book stays as it was. With splice, you cut pages out of the book itself, and maybe glue new pages in.
- In real life: Photocopy pages 2 to 4
- In JavaScript:
array.slice(1, 4)gives back copies of the items at indexes 1, 2, and 3 - In real life: The book stays on the shelf, unchanged
- In JavaScript:
slicedoesn't change the original array - In real life: Cut pages out of the book and glue new pages in
- In JavaScript:
array.splice(start, deleteCount, ...items)changes the original array - In real life: The pages you cut out land on your desk
- In JavaScript:
splicegives back an array of the removed items
slice(start, end) copies items into a new array:
- It starts at index
start. - It stops just before index
end. The item atendis not included. - If you leave out
end, it copies all the way to the end of the array. - A negative index counts from the end.
slice(-2)means "the last two items".
splice(start, deleteCount, ...items) changes the array itself:
- It starts at index
start. - It removes
deleteCountitems from there. - It puts any
itemsyou pass in their place. - It gives back a new array that holds the removed items. It does not give back the changed array. Many bugs come from forgetting this.
const letters = ["a", "b", "c"];
const middle = letters.slice(1, 2);
console.log(middle.join(""));
console.log(letters.join(""));Line 1 makes letters. Line 2 copies from index 1 up to, but not including, index 2. That is just "b". Line 3 prints b. Line 4 prints abc, so slice did not change letters. The join("") puts the letters together with nothing between them.
const todo = ["write", "test", "ship"];
const removed = todo.splice(1, 1, "review");
console.log(removed[0]);
console.log(todo.join(","));Line 2 starts at index 1, removes 1 item ("test"), and puts "review" in its place. It gives back ["test"], which is saved in removed. Line 3 prints test. Line 4 prints write,review,ship. Unlike slice, this call changed todo.
["a", "b", "c", "d"] and give back b and c. slice copies them, so letters still has all four letters. splice cuts them out, so letters is left with a and d.const letters = ["a", "b", "c", "d"];letters.slice(start, end);letters.splice(start, deleteCount, ...items);["b","c"]["a","b","c","d"]slice gave back a copy of some items. The original array did not change, even with a negative start.
["a", "b", "c", "d"], so you can compare the two methods fairly.Try setting start to -2. Then slice copies from the second-to-last item, and splice starts cutting at the second-to-last item. Each button runs the real method on a fresh array. So the "return value" box shows what the method really gave back, and the "original after" box shows what the array really holds now.
concat and spread: put lists together in a new list
STEP THROUGHconst first = ["tea"];
const second = ["bread"];
const breakfast = first.concat(second);
console.log(breakfast.join(","));
console.log(first.join(","));Line 3 calls first.concat(second). It makes a new array with the items of first, then the items of second. Line 4 prints tea,bread. Line 5 prints tea, so first did not change.
There are two ways to put arrays together without changing them:
- Spread:
[...a, ...b]. Each...opens one array and places its items inside the new square brackets. concat:a.concat(b, 4). It starts with the items ofa, adds the items ofb, and then adds the value4.
Both give back a new outer array.
Picture taping two shopping lists onto a new sheet of paper. The old lists still exist. Now say one photo is taped to the old list and to the new sheet. There is still only one photo. If you draw on it, both sheets show the drawing.
- In real life: Tape two shopping lists onto a fresh sheet
- In JavaScript:
[...a, ...b]makes a new outer array - In real life: Write one more item at the bottom
- In JavaScript:
a.concat(b, 4)can add arrays and single values - In real life: A photo taped to both sheets is the same photo
- In JavaScript: An inner array or object in both arrays is shared
Guess which arrays change. Then step through spread, concat, and one shallow copy.
script
const b = [3];const spread = [...a, ...b];const concatted = a.concat(b, 4);const nested = [["Ada"], ["Lin"]];const copy = nested.slice();copy[0].push("Grace");console.log(JSON.stringify(spread), JSON.stringify(concatted), JSON.stringify(nested[0]));Lines 3 and 4 hold no surprises. [...a, ...b] gives [1, 2, 3], and a.concat(b, 4) gives [1, 2, 3, 4]. Lines 5 to 7 show the catch. slice, spread, and concat make shallow copies. If an item is an object or an inner array, the copy and the original share it. Line 7 adds "Grace" through copy[0], and nested[0] shows it too.
nested and copy are two different outer arrays. But slot [0] in each one points to the same inner array. So when line 7 adds "Grace" through copy[0], nested[0] shows it too.join and split: turn arrays into text and back
INTERACTIVEconst tags = "js,arrays".split(",");
console.log(tags.join(" | "));Line 1 cuts the text "js,arrays" at each comma. That gives the array ["js", "arrays"]. Line 2 puts the items back together with " | " between them, and prints js | arrays.
join("-") puts the items into one string, with - between them. split("-") cuts that string at every - and gives back an array.Think of array items as beads. join threads them onto one string, with a separator between each pair. split does the opposite: it cuts a string wherever it finds the separator.
- In real life: Thread beads with a dash between each pair
- In JavaScript:
["a", "b", "c"].join("-")gives back"a-b-c" - In real life: No spacer chosen? Put a comma between beads
- In JavaScript:
join()with no separator puts commas between items - In real life: Cut the necklace at every comma
- In JavaScript:
"a,b,,c".split(",")gives back four pieces
Here are some checked results from join:
["a", "b", "c"].join("-")givesa-b-c.join()with no separator uses a comma:a,b,c.[null, undefined, 1].join("-")gives--1.nullandundefinedturn into empty text, but the dashes stay.
And here are some from split:
"a,b,,c".split(",")gives["a","b","","c"]. Two commas in a row have nothing between them, so you get an empty string,""."a,b,,c".split(",", 2)gives["a","b"]. The second argument is a limit: the most pieces you want back."hi".split("")gives["h","i"]. An empty separator cuts between every character.
Emoji are different. One emoji can come out as more than one piece with split(""). The Unicode & string internals lesson explains why.
const pieces = ["a", "b", "c"];pieces.join("-");"a,b,,c".split(",");"a,b,c"["a","b","","c"]join gives back one string. split gives back an array of strings. An empty piece shows up as "".
-, a space, and an empty separator. An empty separator works for plain letters. Emoji need the Unicode lesson that comes later.Array.from and Array.of: build new arrays
STEP THROUGHconst prices = Array.from({ length: 3 }, (_, index) => (index + 1) * 10);
console.log(prices.join(","));Line 1 asks for a new array of length 3. For each index, 0, 1, and 2, the arrow function works out one price: (index + 1) * 10. The function's first argument would be the item. Here every item is undefined, so it gets the name _, which means "not used". Line 2 prints 10,20,30.
Array.from makes a new array from another value. It accepts two kinds of value:
- An iterable: a value you can loop over one item at a time, such as a string.
Array.from("hey")gives["h", "e", "y"]. - An array-like object: an object with a
lengthproperty.Array.from({ length: 3 }, (_, i) => i * 2)gives[0, 2, 4].
A Set is iterable too. A Set is a collection that keeps each value only once. Array.from(new Set([1, 1, 2])) gives [1,2]. The Set dropped the second 1 before Array.from read it. Sets get their own lesson later.
Array.of avoids a surprise you met in Array basics:
Array.of(3)makes an array with one item, the number 3:[3].new Array(3)makes an array with length 3 but no items. Its three slots are empty.
This mix-up causes real bugs, so it is worth seeing again.
Pick a length and guess the evens array. Then step through the ways to build arrays and the fill trap.
script
const evens = Array.from({ length: 4 }, (_, i) => i * 2);const oneNumber = Array.of(3);const emptySlots = new Array(3);const rows = new Array(3).fill({ done: false });rows[0].done = true;const shifted = [1, 2, 3, 4, 5].copyWithin(0, 3);console.log(letters, evens, oneNumber, emptySlots.length, rows[1].done, shifted);fill and copyWithin: two tools that change the array
const seats = new Array(3).fill("open");
seats[1] = "taken";
console.log(seats.join(","));Line 1 makes an array with 3 empty slots. Then fill writes "open" into each slot. Line 2 changes slot 1 to "taken". Line 3 prints open,taken,open. This works well because "open" is a string. A string is a primitive value: it has no parts that one slot could change for the others.
fill is handy with primitive values: new Array(3).fill(0) gives [0, 0, 0]. With an object, it is different. fill() does not make a new object for each slot. It puts the same one object in every slot.
- In real life: Paint lockers 2 to 5 blue
- In JavaScript:
array.fill("blue", 1, 5)writes into indexes 1 to 4 - In real life: Every locker gets a key to the same storage room
- In JavaScript:
fill({})puts the same object in every slot - In real life: Copy the labels from one row of lockers onto another row
- In JavaScript:
copyWithincopies items to another place in the same array
The step-through above showed this trap. After rows[0].done = true, rows[1].done is true too. All three slots hold the same object.
rows[0].done = true. On the left, fill put one object in all three slots, so every row now says true. On the right, Array.from made a new object for each slot, so only row 0 changes.The fix is to make a new object for each slot. Array.from calls your function once per slot, so each call makes a new object:
const rows = Array.from({ length: 3 }, () => ({ done: false }));copyWithin is used less often. It copies some items to another place in the same array, and it changes that array. For example, [1, 2, 3, 4, 5].copyWithin(0, 3) copies the items from index 3 onward, 4 and 5, to index 0. The array becomes [4,5,3,4,5], and the method gives back that same array. You may not write it often. When you read it, remember that it changes the array.
Where you'll use this
Apps keep lists all the time: cart items, chosen filters, open tabs, songs in a playlist, rows in a form, and text a person typed. These tools work on those lists.
const items = ["red", "green", "blue", "gold"];const index = 1;const next = [...items.slice(0, index), ...items.slice(index + 1)];Line 3 copies the items before index 1 and the items after index 1. Then it spreads both copies into a new array: ["red", "blue", "gold"]. items still has all four colors, so other code that uses it keeps working. Later, the Sorting & copying safely lesson shows toSpliced, a newer method that does the job of splice without changing the original.
- Use
splitto cut text a person typed into tags, andjointo show the cleaned tags as text. - Use
concator spread to add new results from a server to a list, without changing the old list. - Use
Array.fromto make placeholder rows, with a new object for each row. - Use
sliceto show the first few cards while keeping the full list.
slice()with no arguments makes a shallow copy of the whole array.splice(index)with no count removes everything fromindexto the end. Pass a count, likesplice(index, 1), when you mean one item.conststops you from putting a different array in the variable. It does not stopspliceorfillfrom changing the array.- Removing the item at index
0makes every other item move down one place. On a very large list, doing this many times can be slow, so measure it before you rely on it. joinmakes plain text, not safe HTML. Show text a person typed withtextContent, not by building an HTML string.toSpliceddoes the job ofsplicewithout changing the original. Check that the browsers you support have it. The sorting lesson compares it withsplice.
Common misconceptions
"slice and splice do the same thing."
They don't. slice copies items and leaves the array alone. splice changes the array and gives back the removed items.
"A copied array shares nothing with the original."
The copies in this lesson are shallow. If an item is an object or an inner array, the copy and the original share it.
"join writes null and undefined as words."
join turns null and undefined into empty text. So two separators can end up side by side.
"split always drops empty pieces."
It keeps them. "a,b,,c".split(",") includes an empty string, "".
"fill() makes a separate object for each slot."
It puts the same one object in every slot. Use Array.from with a function that makes a new object each time.
"copyWithin gives back a safe copy."
It changes the array and gives back that same array. "Copy" here means copying items inside the same array.
items.slice(1, 3)items.splice(1, 2, "x")a.concat(b)[...a, ...b]items.join('-')items.fill(0)items.copyWithin(0, 2)Array.from('hey')
Put each line in a group. Read the explanation even when you get it right; it covers the tricky cases.
Practice: copy, cut, join, and build
5 EXERCISESPredict all three logs. Type the first log in the answer box, then check the solution for all three.
const letters = ["a", "b", "c", "d"];
const copied = letters.slice(1, 3);
const removed = letters.splice(2, 1, "X");
console.log(copied.join(""));
console.log(removed.join(""));
console.log(letters.join(""));The first log is bc. splice removes c, so the second log is c. splice also changed the original to a,b,X,d, so the third log is abXd.
Remove green at index 1 without using splice. What does the new array print?
const items = ["red", "green", "blue", "gold"];
const index = 1;
const withoutGreen = [...items.slice(0, index), ...items.slice(index + 1)];
console.log(withoutGreen.join(","));
console.log(items.join(","));const items = ["red", "green", "blue", "gold"];
const index = 1;
const withoutGreen = [...items.slice(0, index), ...items.slice(index + 1)];
console.log(withoutGreen.join(","));
console.log(items.join(","));The new array skips green and prints red,blue,gold. The original still prints red,green,blue,gold.
Turn "a b c" into ["a", "b", "c"], then back into one string with | between the letters.
const words = "a b c".split(" ");
console.log(words.join("|"));const words = "a b c".split(" ");
console.log(words.join("|"));split(" ") makes ["a", "b", "c"]. Then join("|") makes a|b|c.
Build [0, 2, 4, 6] without typing the four numbers yourself.
const evens = Array.from({ length: 4 }, (_, i) => i * 2);
console.log(evens.join(","));const evens = Array.from({ length: 4 }, (_, i) => i * 2);
console.log(evens.join(","));The indexes are 0, 1, 2, and 3. Multiplying each one by 2 gives [0, 2, 4, 6].
Change the first line so that changing the first row does not change the second row.
const rows = new Array(3).fill({ done: false });
rows[0].done = true;
console.log(rows[1].done);const rows = Array.from({ length: 3 }, () => ({ done: false }));
rows[0].done = true;
console.log(rows[1].done);The function runs once for each slot, so rows[0] and rows[1] are different objects. After row 0 changes, row 1 still prints false.
Quiz: check your understanding
8 QUESTIONSFor each question, follow two things one at a time: what the method gives back, and what the original array holds after. Your first answer is the one that counts, and every choice explains why it is right or wrong.
Question 1 of 8Which sentence describes
sliceandsplice?Choose an answer to see the explanation.
Question 2 of 8What does
slice(-2)give back, and what is left inletters?Read the code, then predictconst letters = ["a", "b", "c"]; console.log(letters.slice(-2).join("")); console.log(letters.join(""));Choose an answer to see the explanation.
Question 3 of 8What does
splicegive back here, and what is left innums?Read the code, then predictconst nums = [1, 2, 3, 4]; console.log(nums.splice(1, 2, 9).join(",")); console.log(nums.join(","));Choose an answer to see the explanation.
Question 4 of 8What does
joinprint when the array holdsnullandundefined?Read the code, then predictconsole.log([null, undefined, 1].join("-"));Choose an answer to see the explanation.
Question 5 of 8What does
splitprint with no limit, and then with a limit of 2?Read the code, then predictconsole.log("a,b,,c".split(",").join("|")); console.log("a,b,,c".split(",", 2).join("|"));Choose an answer to see the explanation.
Question 6 of 8Which code makes
[3], an array with one item, the number 3?Choose an answer to see the explanation.
Question 7 of 8After
fillwith an object, what doesrows[1].doneprint?Read the code, then predictconst rows = new Array(2).fill({ done: false }); rows[0].done = true; console.log(rows[1].done);Choose an answer to see the explanation.
Question 8 of 8What does
copyWithingive back, and what is left invalues?Read the code, then predictconst values = [1, 2, 3, 4, 5]; console.log(values.copyWithin(0, 3).join(",")); console.log(values.join(","));Choose an answer to see the explanation.
Key takeaways
slicemakes a shallow copy of part of an array.splicechanges the array and gives back the removed items.- Spread and
concatmake a new outer array, but objects inside it are still shared. joingives back a string.splitgives back an array of strings, and it keeps empty pieces.Array.frombuilds an array from an iterable or an array-like object.Array.of(3)means[3].fillandcopyWithinchange the array. Avoidfill()unless you want every slot to share one object.
Remember the one-line summary.
Copy with slice, spread, concat, and Array.from. Change the array with splice, fill, and copyWithin. Turn arrays into text and back with join and split.
Up next: Searching arrays.