053: Array Methods I: Mutation, Copying, and Search
Outcomes
By the end of this lesson, you can:
- add/remove end elements with
push()andpop(); - add/remove beginning elements with
unshift()andshift(); - copy a range with non-mutating
slice(); - insert, replace, or remove a range with mutating
splice(); - test membership with
includes()and locate values withindexOf(); - state each method's return value; and
- choose intentionally between mutation and a new array.
Prerequisites and Retrieval
Retrieve array literals, indexes, length, mutation, and references.
- What is the first array index?
- Does
const items = []preventitemsfrom being mutated? - Why is
items[items.length]not the final current element?
Use arrays of primitive values today. Object identity and shallow copies become more important in later data lessons.
Terms
- method: Function stored as an object/array property invoked via dot access. — Source: MDN: Method
- mutating method: Changes the receiver array (push/pop/splice/sort/reverse). — Source: MDN: Array — mutating methods
- copying/non-mutating method: Returns a new array leaving the original intact (slice, concat, toSorted). — Source: MDN: Array
- start index: First position included by slice(begin). — Source: MDN: Array.prototype.slice()
- end index: Exclusive boundary where slice stops extracting. — Source: MDN: Array.prototype.slice()
- delete count: Second argument to splice counting removals. — Source: MDN: Array.prototype.splice()
- membership: Testing presence via includes()/indexOf(). — Source: MDN: Array.prototype.includes()
- sentinel: Special return value meaning absence, e.g., indexOf() → -1. — Source: MDN: Array.prototype.indexOf()
- shallow copy: New outer array/object sharing nested references (slice, [...arr]). — Source: MDN: Spread syntax
- String (official): "A string is a sequence of characters used to represent text." — Source: MDN: String
- Template literal (official): "Template literals are string literals allowing embedded expressions using backticks and ${}." — Source: MDN: Template literals
Beginner Explanation and Mental Model
Array methods are tools attached to arrays. For each tool ask three questions:
- Does it mutate the original array?
- What arguments does it expect?
- What does it return?
Memorizing only a method name causes bugs because return values differ.
| Method | Main action | Mutates? | Return value |
|---|---|---|---|
push(a) | add to end | yes | new length |
pop() | remove from end | yes | removed element or undefined |
unshift(a) | add to beginning | yes | new length |
shift() | remove from beginning | yes | removed element or undefined |
slice(start, end) | copy included range | no | new array |
splice(start, count, ...items) | remove/insert in place | yes | array of removed elements |
includes(value) | ask whether value exists | no | Boolean |
indexOf(value) | find first matching index | no | index or -1 |
End and beginning operations
const queue = ["A", "B"];
const lengthAfterPush = queue.push("C"); // queue is A,B,C; returns 3
const last = queue.pop(); // queue is A,B; returns C
const lengthAfterUnshift = queue.unshift("Start"); // returns 3
const first = queue.shift(); // returns Start
All four mutate. Adding/removing at the beginning requires existing indexes to move, so end operations are often a simpler fit when either model is acceptable. Choose based on data semantics, not micro-optimization alone.
slice: end excluded
const letters = ["a", "b", "c", "d"];
const middle = letters.slice(1, 3); // ["b", "c"]
Index 1 is included; index 3 is excluded. The original does not change. Omitting end copies to the end; no arguments makes a shallow outer copy. Negative indexes count from the end.
splice: edit in place
const letters = ["a", "b", "c", "d"];
const removed = letters.splice(1, 2, "x", "y");
// letters: ["a", "x", "y", "d"]
// removed: ["b", "c"]
Read it as: at index 1, remove 2 elements, then insert the remaining arguments. Use splice(index, 1) to remove one known index and splice(index, 0, value) to insert without removal. Do not confuse its second argument with an end index; it is a count.
Search
includes() answers a yes/no question. indexOf() answers where the first match occurs and returns -1 if absent:
const roles = ["viewer", "editor"];
console.log(roles.includes("editor")); // true
console.log(roles.indexOf("editor")); // 1
console.log(roles.indexOf("admin")); // -1
Both compare essentially by strict value matching for normal primitives, with one notable difference: includes(NaN) can find NaN, while indexOf(NaN) returns -1. For beginner lists, search stable primitive values.
Worked Example: Task Queue
const tasks = ["Write code", "Run tests"];
const newLength = tasks.push("Review output");
console.log("After push:", tasks);
console.log("New length:", newLength);
tasks.unshift("Read requirements");
console.log("After unshift:", tasks);
const currentTask = tasks.shift();
console.log("Started:", currentTask);
const completedTask = tasks.pop();
console.log("Removed from end:", completedTask);
console.log("Remaining:", tasks);
console.log("Contains tests:", tasks.includes("Run tests"));
console.log("Tests index:", tasks.indexOf("Run tests"));
Expected output:
After push: ["Write code", "Run tests", "Review output"] New length: 3 After unshift: ["Read requirements", "Write code", "Run tests", "Review output"] Started: Read requirements Removed from end: Review output Remaining: ["Write code", "Run tests"] Contains tests: true Tests index: 1
Browser array formatting varies. Notice that push did not return the added item; it returned length. shift and pop returned removed values. Search methods left the remaining array untouched.
Intermediate Example: Copy and Edit Product Names
const products = ["Mouse", "Keyboard", "Monitor", "Webcam", "Headset"];
const featured = products.slice(1, 4);
console.log("Featured:", featured);
console.log("Original after slice:", products);
const removedProducts = products.splice(2, 2, "Laptop stand", "USB hub");
console.log("Removed:", removedProducts);
console.log("Original after splice:", products);
const searchName = "USB hub";
const searchIndex = products.indexOf(searchName);
if (searchIndex !== -1) {
console.log(`${searchName} found at index ${searchIndex}.`);
} else {
console.log(`${searchName} not found.`);
}
Expected output:
Featured: ["Keyboard", "Monitor", "Webcam"] Original after slice: ["Mouse", "Keyboard", "Monitor", "Webcam", "Headset"] Removed: ["Monitor", "Webcam"] Original after splice: ["Mouse", "Keyboard", "Laptop stand", "USB hub", "Headset"] USB hub found at index 3.
Never write if (products.indexOf(searchName)). Index 0 is falsy even though it means found, and -1 is truthy even though it means absent. Compare explicitly with !== -1, or use includes() when only membership matters.
Optional Advanced Extension: Non-Mutating Splice Alternative
Modern stable JavaScript includes toSpliced(), which uses splice-like arguments but returns a new array:
const original = ["a", "b", "c"];
const updated = original.toSpliced(1, 1, "x");
console.log(original); // ["a", "b", "c"]
console.log(updated); // ["a", "x", "c"]
It is useful when callers expect immutable-style updates. Today's required method is splice(), so first master its mutation and return value. Do not claim slice() can directly insert replacements; it only copies ranges.
Mistakes and Debugging
- Assigning
pushresult as the array:items = items.push(value)makesitemsa number. Callitems.push(value)and keep the original binding. - Expecting
pop/shiftto return an array: each returns one removed element. - Confusing
sliceandsplice:slicecopies;spliceedits. - Treating
sliceend as included: it is excluded. - Treating
splicedelete count as end index: it is how many to remove. - Checking
indexOfby truthiness: compare with-1. - Splicing at
-1after a failed search: this edits from the end. Verify the index first. - Expecting copying methods to deep-clone objects: standard array copy operations are shallow.
- Ignoring mutation in a function: document mutation or return a new array so callers know what happens.
Before and after a method call, log the array and captured return value separately. This reveals most method misunderstandings immediately.
Best Practices
- Choose a method by required semantics, including mutation and return value.
- Use
includes()for yes/no membership andindexOf()when the position is needed. - Check
indexOf()against-1before callingsplice(). - Store removed values when they matter.
- Use
slice()for ranges and shallow outer copies. - Keep
splice()arguments in named variables for nontrivial edits. - Prefer non-mutating operations when shared references or application state make mutation surprising.
- Use
constfor the binding even when deliberately mutating the same array.
Tiered Exercises
Core
Start with ["CSS", "JavaScript"]. Add "HTML" to the beginning, add "Git" to the end, remove both ends while storing returned values, and log every intermediate array and return value.
Practice
Given const cities = ["Chennai", "Madurai", "Salem", "Trichy", "Coimbatore"], copy indexes 1-3 with slice, replace "Salem" and "Trichy" with "Erode" using splice, then search for "Erode" and "Salem".
Professional Extension
Write removeItem(items, item) that mutates the supplied array only if item exists. It should return the removed value, or null when absent. Test an item at index 0, a middle item, and an absent item.
Complete Solutions
const topics = ["CSS", "JavaScript"];
const frontLength = topics.unshift("HTML");
console.log(topics, frontLength);
const endLength = topics.push("Git");
console.log(topics, endLength);
const first = topics.shift();
console.log(topics, first);
const last = topics.pop();
console.log(topics, last);
Final array: ["CSS", "JavaScript"]; removed values: "HTML" and "Git".
const cities = ["Chennai", "Madurai", "Salem", "Trichy", "Coimbatore"];
const middleCities = cities.slice(1, 4);
const removedCities = cities.splice(2, 2, "Erode");
console.log(middleCities); // Madurai, Salem, Trichy
console.log(removedCities); // Salem, Trichy
console.log(cities); // Chennai, Madurai, Erode, Coimbatore
console.log(cities.includes("Erode")); // true
console.log(cities.indexOf("Erode")); // 2
console.log(cities.includes("Salem")); // false
console.log(cities.indexOf("Salem")); // -1
function removeItem(items, item) {
const index = items.indexOf(item);
if (index === -1) {
return null;
}
const removedItems = items.splice(index, 1);
return removedItems[0];
}
const values = ["a", "b", "c", "d"];
console.log(removeItem(values, "a")); // a
console.log(removeItem(values, "c")); // c
console.log(removeItem(values, "z")); // null
console.log(values); // ["b", "d"]
Checking -1 prevents an absent item from accidentally removing the last element.
Recap and Exit Questions
Methods differ in action, mutation, and return value. End/front methods mutate, slice copies an end-exclusive range, splice edits in place and returns removed elements, includes answers membership, and indexOf returns a position or -1.
- Which required methods mutate the original array?
- What do
push()andpop()return? - Why does
slice(1, 3)omit index 3? - What does the second
spliceargument mean? - Why is
if (items.indexOf(value))incorrect?
Official References
- MDN:
Array - MDN: Indexed collections, array methods
- MDN:
slice() - MDN:
splice() - MDN:
includes() - MDN:
indexOf() - ECMA-262: properties of the Array prototype object
References checked 2026-08-24.
