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.
- 01Choose mutating or non-mutating toolsPredict exactly when
slice,splice,concat,fill, andcopyWithinchange the original array. - 02Move between arrays and stringsUse
joinandsplitwith separators, defaults, empty fields, and limits. - 03Create arrays intentionallyUse spread,
Array.from, andArray.ofwithout falling intonew Array(3)or shared-objectfillbugs.
Copy parts, change parts, and turn lists into text
Arrays are ordered lists. In Array basics you met the list itself: indexes, length, adding items, removing items, and why holes from new Array(3) are surprising. This lesson is about the next everyday skill: choosing the right method when you need a piece of a list, a changed list, or a string made from list items.
The summary for this lesson is short but powerful: copy parts of an array, or change it in place. The important words are “copy” and “in place.” A method that copies returns a new value. A method that works in place mutates the original array: the same array object now holds different contents.
| Tool | Changes the receiver? | Returns | Memory note |
|---|---|---|---|
slice(start, end) | No | A shallow copy of the selected range | Nested objects are shared |
splice(start, deleteCount, ...items) | Yes | The removed items | Use carefully when other code keeps the same array |
concat(...) | No | A new shallow array | Arrays passed to concat are spread one level |
join(separator) | No | A string | null and undefined become empty text |
split(separator, limit) | No string mutation | A new array of strings | Empty pieces are kept |
fill(value, start, end) | Yes | The same array | One object value is shared in every filled slot |
copyWithin(target, start, end) | Yes | The same array | Copies inside the same array |
Array.from(value, mapFn) | No | A new array | Good for array-likes and per-slot objects |
Spread, slice, and concat create a new outer array, but they do not deeply clone objects inside. That builds directly on Objects & references.
You will also reuse ideas from Strings and Default & rest parameters: separators turn arrays into strings, and spread expands values into a new array.
slice vs splice: photocopy or surgery?
INTERACTIVEslice and splice differ by one letter, so learners mix them up constantly. Say the story out loud: slice is a photocopier; splice is surgery. One returns a copied range. The other edits the patient.
- In real life: Photocopy pages 2 through 4
- In JavaScript:
array.slice(1, 4)returns copies of those items - In real life: The original book stays on the shelf
- In JavaScript:
slicedoes not mutate the original array - In real life: Cut pages out and glue new pages in
- In JavaScript:
array.splice(start, deleteCount, ...items)mutates - In real life: The removed pages land on your desk
- In JavaScript:
splicereturns an array of removed items
Where the analogy stops: A photocopy duplicates paper. JavaScript's array copy is shallow: object elements are still shared references. You'll see that in the next section.
slice(start, end) starts at start and stops before end. The end is not included. Leave out end to copy to the end. Use a negative index to count from the right: slice(-2) means “last two items.”
splice(start, deleteCount, ...items) starts at start, removes deleteCount items, inserts any extra items, returns the removed items, and mutates the original array. That return value is a common source of bugs.
const letters = ["a", "b", "c", "d"];letters.slice(start, end);letters.splice(start, deleteCount, ...items);["b","c"]["a","b","c","d"]slice returned a copied range. The original boxes are unchanged, even with a negative start.
["a", "b", "c", "d"] before running, so you can compare the methods fairly.Try start = -2. With slice, you copy from the second-to-last item. With splice, you cut starting near the end. The lab runs real arrays each time, so the return value and the “original after” panel are the JavaScript result, not a drawing.
concat & spread: tape lists into a new list
STEP THROUGHTo combine arrays without changing the originals, use spread or concat. Spread opens an array inside a literal: [...a, ...b]. concat reads from a base array: a.concat(b, 4). Both return a new outer array.
Imagine taping two lists onto a new page. The original pages still exist. But if both pages point to the same attached photo, scribbling on the photo changes what both pages show.
- In real life: Tape two shopping lists onto a fresh sheet
- In JavaScript:
[...a, ...b]creates a new outer array - In real life: Add one handwritten item at the end
- In JavaScript:
a.concat(b, 4)can append arrays and values - In real life: A photo taped to both lists is the same photo
- In JavaScript: Nested arrays and objects are shared references
Where the analogy stops: Tape creates a new sheet, not new copies of every object mentioned on the sheet. That is the shallow-copy limit.
Predict which arrays change. Step through spread, concat, and one shallow-copy surprise.
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]));The first two results are unsurprising: [...a, ...b] gives [1, 2, 3], and a.concat(b, 4) gives [1, 2, 3, 4]. The nested part matters professionally: slice, spread, and concat are shallow. If an item is an object or inner array, the copy and original share it.
join & split: strings and arrays trade places
INTERACTIVEjoin takes array “beads” and threads one string through them. split takes a string necklace and cuts wherever the separator appears.
- In real life: Thread beads with a dash between them
- In JavaScript:
["a", "b", "c"].join("-")returns"a-b-c" - In real life: Use plain string between beads by default
- In JavaScript:
join()defaults to comma - In real life: Cut the necklace wherever you see a comma
- In JavaScript:
"a,b,,c".split(",")returns four pieces
Where the analogy stops: Real beads disappear when cut incorrectly. JavaScript strings are immutable: split returns a new array and leaves the original string alone.
Verified examples: ["a", "b", "c"].join("-") returns a-b-c; join() uses comma and returns a,b,c; [null, undefined, 1].join("-") returns --1 because null and undefined become empty text.
Going the other way, "a,b,,c".split(",") returns ["a","b","","c"]. The empty field between adjacent commas is kept. With a limit of 2 it returns ["a","b"]. And "hi".split("") returns ["h","i"]. For emoji, wait for Unicode & string internals: visible characters are not always one code unit.
const pieces = ["a", "b", "c"];pieces.join("-");"a,b,,c".split(",");"a,b,c"["a","b","","c"]join returns one string. split returns an array of strings. Empty pieces stay visible as "".
-, a space, and an empty separator. Empty separator is useful for simple text, but emoji need the later Unicode lesson.Array.from & Array.of: create arrays on purpose
STEP THROUGHArray.from creates a new array from something iterable or array-like. A string is iterable, so Array.from("hey") becomes ["h", "e", "y"]. An object with a length can be array-like, so Array.from({ length: 3 }, (_, i) => i * 2) becomes [0, 2, 4]. A Set is iterable too: [1,2] comes from Array.from(new Set([1, 1, 2])). The Set lesson comes later; for now, notice that the duplicate disappeared before Array.from read it.
Array.of fixes one old constructor surprise: Array.of(3) means “an array containing the item 3.” new Array(3) means “an array whose length is 3 but whose slots are empty.” That pitfall came from Array basics, and it is worth repeating because it causes real bugs.
Change the length, predict the evens array, then step through the creation methods and mutation traps.
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 & copyWithin: useful, mutating, and easy to misuse
fill is handy for primitive values: new Array(3).fill(0) gives [0, 0, 0]. But fill() puts the same object in every slot.
- In real life: Paint lockers 2 through 5 blue
- In JavaScript:
array.fill("blue", 1, 5)writes into that range - In real life: Use the same stencil in every locker
- In JavaScript:
fill({})puts the same object reference everywhere - In real life: Copy labels from one row of lockers to another
- In JavaScript:
copyWithincopies inside the same array
Where the analogy stops: Paint is a primitive-like value in the story. Objects are different: one object reference can be seen from many slots.
The replay above shows the trap: after rows[0].done = true, rows[1].done is also true. The fix is to create a fresh object for each slot:
const rows = Array.from({ length: 3 }, () => ({ done: false }));copyWithin is rarer. It copies a slice of an array into another position inside the same array and mutates that array. A verified example: [1, 2, 3, 4, 5].copyWithin(0, 3) returns and leaves [4,5,3,4,5]. You may not write it often, but when you read it, know that it is in-place.
Where you’ll use this
These methods show up any time a user interface keeps lists: cart items, selected filters, open tabs, playlist songs, form rows, and text entered by a person.
const items = ["red", "green", "blue", "gold"];const index = 1;const next = [...items.slice(0, index), ...items.slice(index + 1)];That pattern keeps the old array available for code that still needs it. The upcoming Sorting & copying safely lesson teases toSpliced, a newer non-mutating cousin of splice.
splituser text into tags, thenjoincleaned tags for display.concator spread server results onto an existing list without changing the old one.Array.fromcreate placeholder rows safely, one fresh object at a time.slicepreview the first few cards while keeping the full list intact.
Common misconceptions
“slice and splice are basically the same.”
No. slice copies; splice mutates and returns removed items.
“If I copied the array, nothing inside is shared.”
Array copies here are shallow. Object and nested-array elements are still shared references.
“join turns null and undefined into words.”
join treats null and undefined as empty text, so separators may sit next to each other.
“split always removes empty pieces.”
Empty pieces are kept. "a,b,,c".split(",") includes "".
“fill() makes separate objects.”
It writes the same object reference into every slot. Use Array.from with a factory function instead.
“copyWithin returns a safe copy.”
It mutates and returns the same array. The word “copy” means copied inside the receiver.
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')
Sort each expression. Read the explanation even when you guess correctly; the edge cases matter.
Practice: copy, cut, join, and create
5 EXERCISESPredict all three logs. Use the answer box for the first log, then check the solution for the full trace.
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. The original was mutated 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 pipes.
const words = "a b c".split(" ");
console.log(words.join("|"));const words = "a b c".split(" ");
console.log(words.join("|"));split(" ") creates ["a", "b", "c"], then join("|") creates a|b|c.
Build [0, 2, 4, 6] without writing the four numbers by hand.
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 by 2 creates [0, 2, 4, 6].
Change the setup so mutating the first row does not affect 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 factory function runs once per slot, so rows[0] and rows[1] are different objects. After changing row 0, row 1 still prints false.
Quiz: check your understanding
8 QUESTIONSTrace return values and original arrays separately. Your first try counts, and every option explains itself.
Question 1 of 8Which statement best describes
sliceandsplice?Choose an answer to see the explanation.
Question 2 of 8What do
slice(-2)and the untouched original print?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 do the
splicereturn value and final array print?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
[null, undefined, 1].join('-')produce?Read the code, then predictconsole.log([null, undefined, 1].join("-"));Choose an answer to see the explanation.
Question 5 of 8What do
splitwith and without a limit print?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 expression creates
[3]instead of an empty-slotted array of length 3?Choose an answer to see the explanation.
Question 7 of 8What does the shared-object
fillexample print?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
copyWithinprint before and after?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
slicecopies a shallow range;splicemutates and returns removed items.- Spread and
concatmake new outer arrays, but nested objects stay shared. joinreturns a string;splitreturns an array of strings and keeps empty pieces.Array.frombuilds arrays from iterables or array-likes;Array.of(3)means[3].fillandcopyWithinmutate. Avoidfill()unless shared objects are intentional.
Remember the one-liner.
Copy with slice, spread, concat, and Array.from; mutate with splice, fill, and copyWithin; bridge arrays and strings with join and split.
Up next: Searching arrays.