How to Work with Arrays in JavaScript
Arrays are one of the most essential data structures in JavaScript. They store ordered collections of values, where each element has a numeric index starting from zero. Whether you are managing a list of users, processing API data, storing shopping cart items, or handling any sequence of related values, arrays are the tool you reach for.
Unlike objects, which store data as unordered key-value pairs, arrays maintain a specific order and provide optimized methods for adding, removing, and iterating over elements. JavaScript arrays are also remarkably flexible: they can hold values of any type, grow and shrink dynamically, and even contain other arrays for multi-dimensional structures.
This guide covers everything you need to know about arrays as a data structure: how to create them, access and modify elements, work with length, add and remove items from both ends, iterate effectively, and avoid the common mistakes that trip up developers at every level.
What Is an Array? Creating Arrays
An array is an ordered list of values. Each value is called an element, and each element has a numeric index that represents its position, starting from 0.
Array Literal Syntax (Preferred)
The most common and recommended way to create arrays is with square brackets:
// Empty array
let empty = [];
// Array with initial elements
let fruits = ["apple", "banana", "cherry"];
// Arrays can hold any type
let mixed = [1, "hello", true, null, { name: "Alice" }, [1, 2, 3]];
// Numbers
let scores = [95, 88, 72, 100, 63];
// Each element on its own line (common for longer arrays)
let colors = [
"red",
"green",
"blue",
"yellow",
];
The Array Constructor
You can also create arrays using the Array constructor, though this is less common and has a confusing behavior with a single numeric argument:
// With multiple arguments (creates an array with those elements)
let arr1 = new Array("apple", "banana", "cherry");
console.log(arr1); // ["apple", "banana", "cherry"]
// ⚠️ With a SINGLE numeric argument (creates an empty array with that LENGTH)
let arr2 = new Array(3);
console.log(arr2); // [empty × 3] (sparse array, no actual elements)
console.log(arr2.length); // 3
console.log(arr2[0]); // undefined
// This is confusing (avoid using new Array() with a single number)
let arr3 = new Array("3");
console.log(arr3); // ["3"] (single string element, not length!)
Use the [] literal syntax for creating arrays. It is cleaner, more predictable, and avoids the confusing single-argument behavior of the Array constructor.
Array.of(): Consistent Element Creation
Array.of() always creates an array from its arguments, regardless of the number or type:
let a = Array.of(3); // [3] (single element, NOT length)
let b = Array.of(1, 2, 3); // [1, 2, 3]
let c = Array.of("hello"); // ["hello"]
console.log(a); // [3]
console.log(b); // [1, 2, 3]
This solves the new Array(3) ambiguity, but in practice, the literal syntax [3] is simpler and more common.
Accessing and Modifying Elements by Index
Array elements are accessed using bracket notation with zero-based indices:
let fruits = ["apple", "banana", "cherry", "date"];
// Reading elements
console.log(fruits[0]); // "apple" (first element)
console.log(fruits[1]); // "banana"
console.log(fruits[3]); // "date" (last element)
// Out-of-bounds access returns undefined (no error)
console.log(fruits[10]); // undefined
console.log(fruits[-1]); // undefined (negative indices don't work with brackets)
Modifying Elements
You can change any element by assigning to its index:
let fruits = ["apple", "banana", "cherry"];
fruits[1] = "blueberry"; // Replace the second element
console.log(fruits); // ["apple", "blueberry", "cherry"]
fruits[0] = "avocado";
console.log(fruits); // ["avocado", "blueberry", "cherry"]
Adding Elements by Index
You can assign to an index beyond the current length, but this creates a sparse array with empty slots:
let arr = ["a", "b", "c"];
arr[5] = "f"; // Skips indices 3 and 4
console.log(arr); // ["a", "b", "c", empty × 2, "f"]
console.log(arr.length); // 6
console.log(arr[3]); // undefined (empty slot)
console.log(arr[4]); // undefined (empty slot)
Avoid creating sparse arrays. They lead to confusing behavior and are less performant than dense arrays.
Using at() for Negative Indices
The at() method (ES2022) supports negative indices, counting from the end:
let fruits = ["apple", "banana", "cherry", "date"];
console.log(fruits.at(0)); // "apple"
console.log(fruits.at(-1)); // "date" (last element)
console.log(fruits.at(-2)); // "cherry" (second to last)
// Compare with the old way:
console.log(fruits[fruits.length - 1]); // "date" (verbose9
console.log(fruits.at(-1)); // "date" (clean)
Array Length and Its Writable Nature
The length property returns the number of elements in an array. More precisely, it is one greater than the highest numeric index:
let arr = ["a", "b", "c", "d", "e"];
console.log(arr.length); // 5
let empty = [];
console.log(empty.length); // 0
Length Is Writable
Unlike most properties, length can be written to, which modifies the array:
Decreasing length truncates the array:
let arr = ["a", "b", "c", "d", "e"];
arr.length = 3;
console.log(arr); // ["a", "b", "c"] ("d" and "e" are permanently removed!)
arr.length = 5;
console.log(arr); // ["a", "b", "c", empty × 2] (elements are NOT restored!)
console.log(arr[3]); // undefined
The truncation is irreversible. Setting the length back to a higher value does not restore the removed elements; it just creates empty slots.
Increasing length adds empty slots:
let arr = [1, 2, 3];
arr.length = 6;
console.log(arr); // [1, 2, 3, empty × 3]
console.log(arr.length); // 6
console.log(arr[4]); // undefined
Quick Way to Clear an Array
Setting length to 0 is the fastest way to empty an array while keeping the same reference:
let arr = [1, 2, 3, 4, 5];
let ref = arr; // ref points to the same array
arr.length = 0;
console.log(arr); // []
console.log(ref); // [] (same array, also emptied)
// Compare with reassignment (creates a NEW array):
let arr2 = [1, 2, 3, 4, 5];
let ref2 = arr2;
arr2 = [];
console.log(arr2); // []
console.log(ref2); // [1, 2, 3, 4, 5] (ref2 still points to the original!)
length Is DestructiveSetting length to a smaller value permanently removes elements. This cannot be undone. Be intentional when modifying length directly.
Trailing Commas and Sparse Arrays
Trailing Commas
JavaScript allows a trailing comma after the last element in an array. This is considered good practice because it makes diffs cleaner in version control:
let fruits = [
"apple",
"banana",
"cherry", // Trailing comma (perfectly valid)
];
console.log(fruits.length); // 3 (trailing comma does NOT add an element)
Sparse Arrays
A sparse array has "holes" where indices are skipped. They are created when you delete elements, assign beyond the current length, or use the Array constructor with a single number:
// Creating sparse arrays (all are bad practice)
let sparse1 = [1, , 3]; // Hole at index 1
let sparse2 = new Array(5); // Five empty slots
let sparse3 = [1, 2, 3];
delete sparse3[1]; // Creates a hole at index 1
console.log(sparse1); // [1, empty, 3]
console.log(sparse1[1]); // undefined
console.log(sparse1.length); // 3
console.log(1 in sparse1); // false (index 1 doesn't truly exist!)
console.log(0 in sparse1); // true (index 0 exists)
Sparse arrays behave inconsistently across different array methods. Some methods skip holes, others treat them as undefined. Avoid sparse arrays in production code.
let sparse = [1, , 3];
// forEach SKIPS holes
sparse.forEach((val, i) => console.log(i, val));
// 0 1
// 2 3
// (index 1 is skipped entirely)
// map PRESERVES holes
let mapped = sparse.map(x => x * 2);
console.log(mapped); // [2, empty, 6]
// for...of treats holes as undefined
for (let val of sparse) {
console.log(val);
}
// 1
// undefined
// 3
Sparse arrays cause inconsistent behavior across methods and are harder to debug. If you need to represent missing values, use null or undefined explicitly instead of leaving holes.
Multi-Dimensional Arrays
JavaScript does not have a special syntax for multi-dimensional arrays. Instead, you create arrays of arrays:
// 2D array (matrix)
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// Accessing elements: matrix[row][column]
console.log(matrix[0][0]); // 1 (first row, first column)
console.log(matrix[1][2]); // 6 (second row, third column)
console.log(matrix[2][1]); // 8 (third row, second column)
// Modifying an element
matrix[1][1] = 50;
console.log(matrix[1]); // [4, 50, 6]
Iterating Over a 2D Array
let grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (let row of grid) {
for (let cell of row) {
process.stdout.write(cell + " ");
}
console.log(); // New line after each row
}
// 1 2 3
// 4 5 6
// 7 8 9
3D Arrays and Beyond
let cube = [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
];
console.log(cube[0][1][0]); // 3
console.log(cube[1][0][1]); // 6
Multi-dimensional arrays beyond 2D are uncommon in typical JavaScript applications. When data gets complex, consider using objects or Maps with structured keys instead.
push, pop: Working with the End
push and pop add and remove elements from the end of an array. These are the most commonly used mutation methods and the most performant for modifying arrays.
push(...items): Add to End
let fruits = ["apple", "banana"];
// Add one element
fruits.push("cherry");
console.log(fruits); // ["apple", "banana", "cherry"]
// Add multiple elements at once
fruits.push("date", "elderberry");
console.log(fruits); // ["apple", "banana", "cherry", "date", "elderberry"]
// push returns the new length
let newLength = fruits.push("fig");
console.log(newLength); // 6
pop(): Remove from End
let fruits = ["apple", "banana", "cherry"];
// Remove and return the last element
let last = fruits.pop();
console.log(last); // "cherry"
console.log(fruits); // ["apple", "banana"]
// Pop from an empty array returns undefined
let empty = [];
console.log(empty.pop()); // undefined
Stack Pattern (LIFO)
push and pop together implement a stack (Last In, First Out):
let stack = [];
stack.push("first");
stack.push("second");
stack.push("third");
console.log(stack); // ["first", "second", "third"]
console.log(stack.pop()); // "third" (last in, first out)
console.log(stack.pop()); // "second"
console.log(stack.pop()); // "first"
console.log(stack); // []
shift, unshift: Working with the Beginning
shift and unshift add and remove elements from the beginning of an array.
unshift(...items): Add to Beginning
let fruits = ["cherry", "date"];
// Add one element to the beginning
fruits.unshift("banana");
console.log(fruits); // ["banana", "cherry", "date"]
// Add multiple elements to the beginning
fruits.unshift("apple", "avocado");
console.log(fruits); // ["apple", "avocado", "banana", "cherry", "date"]
// unshift returns the new length
let newLength = fruits.unshift("acai");
console.log(newLength); // 6
shift(): Remove from Beginning
let fruits = ["apple", "banana", "cherry"];
// Remove and return the first element
let first = fruits.shift();
console.log(first); // "apple"
console.log(fruits); // ["banana", "cherry"]
// Shift from an empty array returns undefined
let empty = [];
console.log(empty.shift()); // undefined
Queue Pattern (FIFO)
push and shift together implement a queue (First In, First Out):
let queue = [];
queue.push("first");
queue.push("second");
queue.push("third");
console.log(queue); // ["first", "second", "third"]
console.log(queue.shift()); // "first" (first in, first out)
console.log(queue.shift()); // "second"
console.log(queue.shift()); // "third"
console.log(queue); // []
Performance: push/pop vs. shift/unshift
There is a significant performance difference between operations at the end and operations at the beginning of an array.
Why push/pop Are Fast
push and pop only affect the last element. No other elements need to move:
// push("date"): just adds at the end
// Before: ["apple", "banana", "cherry"]
// ↑ add here
// After: ["apple", "banana", "cherry", "date"]
// pop(): just removes from the end
// Before: ["apple", "banana", "cherry", "date"]
// ↑ remove this
// After: ["apple", "banana", "cherry"]
These operations have a time complexity of O(1), constant time regardless of array size.
Why shift/unshift Are Slow
shift and unshift affect the first element, which means every other element must be re-indexed:
// shift(): removes index 0, then moves every element
// Before: ["apple", "banana", "cherry", "date"]
// ↑ remove this
//
// Step 1: Remove "apple"
// Step 2: Move "banana" from index 1 → 0
// Step 3: Move "cherry" from index 2 → 1
// Step 4: Move "date" from index 3 → 2
//
// After: ["banana", "cherry", "date"]
For an array of 1,000,000 elements, shift needs to re-index 999,999 elements. These operations have a time complexity of O(n), where n is the array length.
Performance Comparison
let large = [];
const SIZE = 100000;
// Filling from the end (fast)
console.time("push");
for (let i = 0; i < SIZE; i++) {
large.push(i);
}
console.timeEnd("push"); // ~5ms
// Emptying from the end (fast)
console.time("pop");
while (large.length > 0) {
large.pop();
}
console.timeEnd("pop"); // ~3ms
// Filling from the beginning (slow)
console.time("unshift");
for (let i = 0; i < SIZE; i++) {
large.unshift(i);
}
console.timeEnd("unshift"); // ~1500ms (hundreds of times slower!)
// Emptying from the beginning (slow)
console.time("shift");
while (large.length > 0) {
large.shift();
}
console.timeEnd("shift"); // ~800ms (hundreds of times slower!)
Prefer push/pop (end operations) over shift/unshift (beginning operations) whenever possible, especially for large arrays. If you need queue-like behavior on large datasets, consider using a linked list or a specialized queue data structure.
Summary of Add/Remove Methods
| Method | Position | Returns | Mutates? | Speed |
|---|---|---|---|---|
push(...items) | End | New length | Yes | O(1) Fast |
pop() | End | Removed element | Yes | O(1) Fast |
unshift(...items) | Beginning | New length | Yes | O(n) Slow |
shift() | Beginning | Removed element | Yes | O(n) Slow |
Iterating: for, for...of, for...in (and Why Not for...in)
There are several ways to loop over array elements. Each has its use case, and one has a critical warning.
Traditional for Loop
The classic for loop gives you full control over the index:
let fruits = ["apple", "banana", "cherry"];
for (let i = 0; i < fruits.length; i++) {
console.log(`${i}: ${fruits[i]}`);
}
// 0: apple
// 1: banana
// 2: cherry
Use when: You need the index, need to skip elements, need to iterate backward, or need to modify the loop counter.
// Iterating backward
let arr = [1, 2, 3, 4, 5];
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i]);
}
// 5, 4, 3, 2, 1
// Skipping every other element
for (let i = 0; i < arr.length; i += 2) {
console.log(arr[i]);
}
// 1, 3, 5
for...of Loop (Preferred for Values)
The for...of loop iterates over values directly, without needing to manage indices:
let fruits = ["apple", "banana", "cherry"];
for (let fruit of fruits) {
console.log(fruit);
}
// apple
// banana
// cherry
Use when: You need the values and do not need the index. This is the cleanest and most readable iteration method for arrays.
If you need both the index and the value, use entries():
let fruits = ["apple", "banana", "cherry"];
for (let [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}
// 0: apple
// 1: banana
// 2: cherry
for...in Loop: Do NOT Use for Arrays
The for...in loop iterates over enumerable property keys of an object. Since arrays are objects, for...in technically works, but it has several critical problems:
let fruits = ["apple", "banana", "cherry"];
// ❌ WRONG: Using for...in on an array
for (let key in fruits) {
console.log(key, typeof key, fruits[key]);
}
// "0" string apple
// "1" string banana
// "2" string cherry
Problem 1: Keys are strings, not numbers
for (let key in fruits) {
console.log(typeof key); // "string", "string", "string" (NOT numbers!)
}
Problem 2: Iterates over ALL enumerable properties, including non-index properties and inherited ones
let arr = [1, 2, 3];
arr.customProp = "oops";
for (let key in arr) {
console.log(key, arr[key]);
}
// "0" 1
// "1" 2
// "2" 3
// "customProp" "oops": this shouldn't appear in array iteration!
Problem 3: Order is not guaranteed for non-integer keys
While modern engines iterate numeric keys in order, the specification does not strictly guarantee iteration order for for...in in all edge cases.
Problem 4: 10 to 100 times slower than for...of
The for...in loop is optimized for generic objects, not arrays. Array-specific loops are significantly faster.
// ❌ NEVER DO THIS
for (let i in myArray) { ... }
// ✅ USE THESE INSTEAD
for (let item of myArray) { ... }
for (let i = 0; i < myArray.length; i++) { ... }
myArray.forEach((item, index) => { ... });
for...in on Arraysfor...in is designed for objects, not arrays. It iterates over all enumerable properties (including inherited and custom ones), returns string keys instead of numeric indices, and is significantly slower. Always use for...of, a traditional for loop, or array methods like forEach.
forEach Method
The forEach method calls a function for each element:
let fruits = ["apple", "banana", "cherry"];
fruits.forEach((fruit, index, array) => {
console.log(`${index}: ${fruit}`);
});
// 0: apple
// 1: banana
// 2: cherry
Note that forEach cannot be stopped early with break or continue. If you need early termination, use a for or for...of loop.
Iteration Methods Comparison
| Method | Access | Break/Continue | Index | Best For |
|---|---|---|---|---|
for | arr[i] | Yes | Yes (manual) | Full control, backward, skip |
for...of | Direct value | Yes | With .entries() | Clean value iteration |
forEach | Callback args | No | Yes (callback arg) | Functional style |
for...in | String keys | Yes | String keys | Never for arrays |
Arrays Are Objects: typeof and Array.isArray()
A critical fact about JavaScript arrays: they are a specialized type of object. This means typeof does not identify them as arrays:
let arr = [1, 2, 3];
let obj = { a: 1, b: 2 };
console.log(typeof arr); // "object" (NOT "array"!)
console.log(typeof obj); // "object"
console.log(typeof arr === typeof obj); // true (same type according to typeof)
Array.isArray(): The Correct Check
To determine whether a value is an array, use Array.isArray():
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray([])); // true
console.log(Array.isArray("hello")); // false
console.log(Array.isArray({ 0: "a", length: 1 })); // false
console.log(Array.isArray(new Array())); // true
Why typeof Fails
Arrays are objects with special behavior: they maintain numeric indices, have an auto-updating length property, and inherit array-specific methods from Array.prototype. But under the hood, they are objects:
let arr = ["a", "b", "c"];
// Arrays have numeric keys (as strings internally)
console.log(Object.keys(arr)); // ["0", "1", "2"]
// You can add non-numeric properties (but you shouldn't)
arr.customProp = "value";
console.log(arr.customProp); // "value"
console.log(arr.length); // 3 (customProp doesn't affect length)
// Arrays use the object prototype chain
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
Practical Type Checking
function processItems(input) {
if (!Array.isArray(input)) {
throw new TypeError("Expected an array, got " + typeof input);
}
// Safe to use array methods
return input.map(item => item.toString());
}
console.log(processItems([1, 2, 3])); // ["1", "2", "3"]
// processItems("hello"); // TypeError: Expected an array...
// processItems({ length: 2 }); // TypeError: Expected an array...
toString and Implicit Conversion
toString()
Arrays have a custom toString() method that joins all elements with commas:
let arr = [1, 2, 3];
console.log(arr.toString()); // "1,2,3"
let mixed = [1, "hello", true, null, undefined];
console.log(mixed.toString()); // "1,hello,true,,"
let nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.toString()); // "1,2,3,4,5,6" (nested arrays are also flattened)
Implicit Conversion
When arrays are used in string or numeric contexts, JavaScript converts them using toString() first:
// String concatenation
console.log("Items: " + [1, 2, 3]); // "Items: 1,2,3"
console.log("" + []); // "" (empty string)
console.log("" + [1]); // "1"
// Numeric conversion (converts to string first, then to number)
console.log(+[]); // 0 (Number("") is 0)
console.log(+[1]); // 1 (Number("1") is 1)
console.log(+[1, 2]); // NaN (Number("1,2") is NaN)
This conversion behavior produces some of JavaScript's most infamous "wat" moments:
console.log([] + []); // "" (both become "" strings, concatenated)
console.log([] + {}); // "[object Object]"
console.log([1] + [2]); // "12" (not 3! "1" + "2" = "12")
console.log([1, 2] + [3, 4]); // "1,23,4" (string concatenation)
These results make perfect sense once you understand that + with a non-numeric operand triggers string conversion, and array toString() returns comma-separated values. But they are surprising at first glance.
Common Mistake: Using == to Compare Arrays
Since arrays are objects, comparing them with == or === checks reference equality, not content equality. Two different arrays with identical contents are not equal:
// ❌ WRONG: This does not compare contents
let a = [1, 2, 3];
let b = [1, 2, 3];
console.log(a === b); // false (different objects in memory)
console.log(a == b); // false (still different references)
// Only true if they are the SAME array
let c = a;
console.log(a === c); // true (same reference)
Output:
false
false
true
How to Compare Array Contents
Simple flat arrays (no nested objects):
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
let arr3 = [1, 2, 4];
console.log(arraysEqual(arr1, arr2)); // true
console.log(arraysEqual(arr1, arr3)); // false
Quick and dirty (works for simple types, not reliable for all cases):
let a = [1, 2, 3];
let b = [1, 2, 3];
console.log(JSON.stringify(a) === JSON.stringify(b)); // true
// ⚠️ Fails for undefined, functions, and order-dependent objects
console.log(JSON.stringify([1, undefined, 3])); // "[1,null,3]"
console.log(JSON.stringify([1, null, 3])); // "[1,null,3]"
// These look the same but contain different values!
Deep comparison (handles nested arrays and objects):
function deepEqual(a, b) {
if (a === b) return true;
if (a === null || b === null) return false;
if (typeof a !== typeof b) return false;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
return a.every((val, i) => deepEqual(val, b[i]));
}
if (typeof a === "object") {
let keysA = Object.keys(a);
let keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(key => deepEqual(a[key], b[key]));
}
return false;
}
console.log(deepEqual([1, [2, 3]], [1, [2, 3]])); // true
console.log(deepEqual([1, [2, 3]], [1, [2, 4]])); // false
Comparing with == Against Primitives
When comparing an array to a primitive using == (loose equality), JavaScript converts the array to a primitive first using toString():
console.log([1] == 1); // true! (toString: "1", Number("1") = 1)
console.log([1, 2] == "1,2"); // true! (toString: "1,2")
console.log([] == 0); // true! (toString: "", Number("") = 0)
console.log([] == false); // true! (toString: "", Number("") = 0, Number(false) = 0)
console.log([] == ""); // true! (toString: "")
// These all make "sense" once you understand the conversion chain,
// but they are extremely confusing and error-prone
Loose equality (==) with arrays produces surprising and dangerous results due to implicit type conversion. Always use strict equality (===) and compare contents manually when needed. Better yet, use utility functions or libraries like Lodash (_.isEqual) for deep comparison.
Additional Pitfalls with Array Comparison
// Empty array in boolean context is truthy (it's an object)
if ([]) {
console.log("Empty array is truthy!"); // This runs!
}
// But empty array == false is true (conversion chain)
console.log([] == false); // true!
console.log(![] == false); // true (![]=false, false==false)
// This leads to the paradox:
if ([]) console.log("truthy"); // runs!
if ([] == false) console.log("wat?"); // also runs!
This is one of the strongest arguments for always using === in JavaScript. With strict equality, [] === false is simply false because the types do not match.
Practical Examples
Building a Simple Todo List
let todos = [];
// Add items
todos.push({ text: "Learn arrays", done: false });
todos.push({ text: "Build a project", done: false });
todos.push({ text: "Read documentation", done: false });
// Mark one as done
todos[0].done = true;
// Filter undone items
let pending = [];
for (let todo of todos) {
if (!todo.done) {
pending.push(todo);
}
}
console.log(pending.length); // 2
// Find a specific item
let found = null;
for (let todo of todos) {
if (todo.text === "Build a project") {
found = todo;
break;
}
}
console.log(found); // { text: "Build a project", done: false }
Processing CSV Data
let csvLine = "Alice,30,Rome,Developer";
let fields = csvLine.split(",");
console.log(fields); // ["Alice", "30", "Rome", "Developer"]
console.log(fields[0]); // "Alice"
console.log(fields.length); // 4
// Reconstruct with different separator
console.log(fields.join(" | ")); // "Alice | 30 | Rome | Developer"
Collecting Unique Values (Preview of Set)
let numbers = [1, 2, 3, 2, 1, 4, 5, 3, 4];
let unique = [];
for (let num of numbers) {
if (!unique.includes(num)) {
unique.push(num);
}
}
console.log(unique); // [1, 2, 3, 4, 5]
// Modern approach (covered in Set article):
// let unique = [...new Set(numbers)];
Summary
- Arrays are ordered collections of values, accessed by zero-based numeric indices. Create them with the literal syntax
[]. - Access elements with bracket notation (
arr[i]) or theat()method (arr.at(-1)for the last element). - The
lengthproperty is writable. Decreasing it truncates the array permanently. Setting it to0clears the array. - Avoid trailing commas confusion and never create sparse arrays (arrays with holes). Use explicit
nullorundefinedfor missing values. push/pop(end operations) are O(1) fast.shift/unshift(beginning operations) are O(n) slow because every element must be re-indexed.- For iteration, use
for...offor values, traditionalforwhen you need index control, andforEachfor functional style. Never usefor...inon arrays. - Arrays are objects under the hood.
typeofreturns"object". UseArray.isArray()for reliable array detection. - Array
toString()returns comma-separated values, which drives the implicit conversion behavior in string and numeric contexts. - Never compare arrays with
==or===. These check reference identity, not content. Write a comparison function or useJSON.stringifyfor simple cases. - Arrays are reference types. Assigning an array to another variable creates a second reference to the same array, not a copy. Use
[...arr]orarr.slice()for shallow copies.