Arrays

An ordered list of values, stored in a single variable.

What is it?

Often you don't just have one value to track — you have many: a list of names, scores, or products. Instead of creating a separate variable for each one, JavaScript gives you an array: a single container that holds an ordered list of values, each accessible by its position (its "index").

Indexes start at 0, so the first item is at position 0, the second at position 1, and so on.

Explain like I'm 10

An array is like a row of numbered lockers. Locker 0 holds the first item, locker 1 the second, and so on. You can look inside any locker directly if you know its number.

Examples

Creating and using an array

const fruits = ["apple", "banana", "cherry"];

console.log(fruits[0]); // "apple"
console.log(fruits.length); // 3

fruits.push("date"); // adds to the end
fruits.pop();        // removes the last item

Looping over an array

const scores = [10, 20, 30];

scores.forEach((score) => {
  console.log(score);
});

How it works

Internally, an array stores its items in order and keeps track of how many there are (its length). Accessing fruits[0] jumps straight to the item at position 0 without needing to check the others.

Why does it exist?

Almost every real program deals with collections of things — a list of users, a shopping cart, search results. Arrays give you a consistent, built-in way to store, access, and process ordered groups of data.

When to use it

Reach for an array whenever you have more than one related value with a natural order — a list of to-dos, a set of scores, the results of a search. If you catch yourself naming variables item1, item2, item3, that's a sign you want an array instead.

When not to use it

If your values aren't really a sequence but a set of named attributes about one thing (a person's name, age, and email), an object is a better fit than an array. And if you need fast lookups by a unique key rather than by position, a Map or object usually serves you better than searching through an array.

Common mistakes

  • Forgetting that array indexes start at 0, not 1.

  • Trying to access an index that doesn't exist (returns undefined instead of an error).

  • Using for...in on an array instead of for...of or .forEach(), which can produce unexpected results.

Practice exercises

  1. Easy:

    Create an array of 5 numbers and log the third one.

  2. Medium:

    Use .map() to create a new array where every number is doubled.

  3. Hard:

    Use .filter() and .reduce() together to sum only the even numbers in an array.

Interview questions

What is an array in JavaScript?

An ordered, list-like structure that stores multiple values in a single variable, where each value is accessed by a numeric position (its index) rather than a name.

Why do array indexes start at 0 instead of 1?

An index isn't a count of position, it's an offset from the start of the array's internal storage — the first item is 0 elements away from the start, the second is 1 element away, and so on.

Does `array.length` update automatically as you add or remove items?

Yes — length is a live property tracked internally by the array, so it's always in sync after push, pop, splice, or a direct assignment like arr[10] = "x".

What's the difference between `push()`/`pop()` and `unshift()`/`shift()`?

push/pop add and remove at the end; unshift/shift do the same at the beginning. Because array indexes are contiguous, adding or removing at the front means every other element has to be re-indexed, making unshift/shift slower than push/pop on large arrays.

What does accessing an index beyond an array's length return?

undefined, not an error — reading arr[i] is just an object-style property lookup, and a missing key resolves to undefined rather than throwing.

What's the difference between `slice()` and `splice()`?

slice(start, end) returns a new array copying out a range and leaves the original untouched; splice(start, deleteCount, ...items) mutates the original array in place, removing and/or inserting items and returning the removed ones.

What does this log? `const a = [1, 2, 3]; const b = a; b.push(4); console.log(a);`

[1, 2, 3, 4]. Arrays are reference types — b = a copies the reference, not the array, so a and b point at the same underlying array, and mutating one is visible through the other.

Why does `typeof []` return `"object"` instead of `"array"`?

JavaScript has no separate array primitive — an array is a specialized kind of object with numeric keys and an auto-updating length, so typeof reports it the same as any other object.

How do you reliably check whether a value is an array?

Array.isArray(value) — since typeof can't distinguish an array from a plain object, this dedicated check inspects the value's internal class instead.

What does this log, and why? `console.log([10, 1, 2].sort());`

[1, 10, 2]. Without a comparator, sort() converts every element to a string and compares them lexicographically, so "10" sorts before "2". Numeric sorting needs an explicit comparator like (a, b) => a - b.

Does `.sort()` mutate the original array?

Yes — sort() (like reverse(), splice(), and fill()) reorders the elements in place and returns the same array reference, rather than producing a new one.

How do you copy an array without mutating the original?

Spread it into a new array literal ([...arr]), use arr.slice() with no arguments, or Array.from(arr) — all three build a brand-new array, but only one level deep.

What does this log? `const original = [{ id: 1 }]; const copy = [...original]; copy[0].id = 2; console.log(original[0].id);`

2. Spread only copies the array's top-level slots; the object stored at index 0 is still the same reference in both arrays, so mutating it through copy is visible through original too.

What happens when you set `array.length = 0`?

It truncates the array to zero elements in place — a fast way to empty an array while keeping the same reference, which matters if other code already holds onto that same array.

What is a 'sparse' array, and how do you accidentally create one?

An array with gaps — empty slots that aren't actually undefined values but simply missing indexes. Writing [1, , 3] or new Array(3) both produce one; length counts the gaps, but some iteration methods treat them differently from a real value.

What does this log? `const arr = [1, , 3]; console.log(arr.length); arr.forEach((n) => console.log(n));`

First 3 (length includes the hole), then just 1 and 3 — forEach (like map and filter) skips empty slots entirely rather than calling the callback with undefined.

What does the `delete` operator do to an array element, and why is it rarely what you want?

delete arr[1] removes the value at that index but leaves a hole — length stays the same and later indexes aren't shifted down. splice(1, 1) is almost always the operation you actually meant.

How does `for...in` differ from `for...of` when looping over an array?

for...in iterates enumerable property keys as strings — including any non-index properties someone added to the array — in no guaranteed order; for...of iterates the actual values in index order, which is what you almost always want for an array.

What does this log? `console.log([NaN].indexOf(NaN)); console.log([NaN].includes(NaN));`

-1 then true. indexOf compares with strict equality, and NaN !== NaN, so it can never find it; includes uses the SameValueZero algorithm, which treats NaN as equal to itself.

How do you convert an array-like object, like `arguments`, into a real array?

Array.from(arguments) or [...arguments] — both build a genuine array with all the array methods, which the original array-like object (which only has indexes and length) doesn't have.

What's the difference between `Array.from()` and `Array.of()`?

Array.from(source, mapFn?) builds an array from an iterable or array-like value, optionally mapping each item; Array.of(...items) builds an array directly from its arguments, which exists specifically to sidestep the new Array(n) ambiguity.

What's confusing about `new Array(7)`?

With a single numeric argument, Array constructs a sparse array of length 7 with no elements, not an array containing the value 7. Array.of(7) or the literal [7] avoid the ambiguity.

What's the difference between `.flat()` and `.flatMap()`?

flat(depth) flattens nested arrays by the given depth (default 1); flatMap(fn) maps every item and then flattens the result by one level in a single pass, more efficient than chaining .map().flat() separately.

How do you access the last item of an array?

Traditionally arr[arr.length - 1]; arr.at(-1) does the same thing more directly, since .at() accepts negative indexes counting back from the end.

What does this log? `function addItem(arr) { arr.push("new"); } const list = ["a", "b"]; addItem(list); console.log(list);`

["a", "b", "new"]. Arrays are passed by reference-copy — arr inside the function points at the same array as list, so a mutating method called on it is visible to the caller.

What does this log? `function replace(arr) { arr = ["x", "y"]; } const list = ["a", "b"]; replace(list); console.log(list);`

["a", "b"], unchanged. arr = ["x", "y"] only repoints the local parameter arr to a brand-new array — it doesn't affect what list points to in the caller, because the reference itself was passed by value.

Why does `[] === []` evaluate to `false`?

=== on arrays (and objects) compares reference identity, not contents — two separate array literals create two separate objects in memory, even if their elements are identical.

How would you compare two arrays for equality by content?

There's no built-in deep-equality check. A shallow comparison is typically a.length === b.length && a.every((v, i) => v === b[i]); JSON.stringify(a) === JSON.stringify(b) works for simple data but breaks down with nested objects, undefined, functions, or differing key order.

Why are `push`/`pop` generally faster than `unshift`/`shift`?

push/pop only touch the end of the array, an O(1) operation; unshift/shift have to shift every remaining element's index up or down by one, making them O(n).

What does this log, and why? `const arr = [1, 2, 3]; arr.forEach((item, i) => { if (i === 0) arr.splice(1, 1); console.log(item); });`

1 then 3 — 2 is skipped. Removing index 1 shifts 3 down into that slot, but forEach's internal counter has already moved on to index 1, which now holds a different element, effectively skipping one. Mutating an array while iterating it is a common source of subtle bugs.

What does this log? `const [a = 5] = [null];`

a is null, not 5 — a destructuring default only kicks in when the corresponding value is undefined (missing), not for other falsy values like null, 0, or "".

How would you remove duplicate values from an array of numbers or strings in one line?

[...new Set(array)] — a Set only ever stores unique values (using SameValueZero equality), so building one from the array and spreading it back removes duplicates without writing a manual loop.

When would you reach for `.find()` instead of `.filter()[0]`?

find() stops iterating and returns the element itself the moment it finds a match (or undefined if none exists); filter() always scans the entire array and builds a whole new array, only for you to immediately discard everything but index 0 — more work for the same result.

Why is chaining several array methods like `.filter().map().reduce()` sometimes worth avoiding on very large arrays?

Each chained call is a full pass over the array producing an intermediate array; combining the logic into a single .reduce() (or a plain loop) does the same work in one pass, trading a bit of readability for avoiding the extra intermediate allocations.