Episode 1 — Fundamentals / 1.21 — Arrays
1.21.a -- Creating Arrays
In one sentence: Arrays are ordered collections of values that you create with literal syntax,
new Array(),Array.of(), orArray.from()-- and because they are reference types, understanding how JavaScript stores them is essential before you manipulate them.
Navigation: <- 1.21 Overview . 1.21.b -- Accessing Elements ->
1. What are arrays?
An array is an ordered list of values. Think of it as a numbered container where each slot holds one item.
Real-world analogies:
| Analogy | Array equivalent |
|---|---|
| Shopping list | Each item is an element; the list is the array |
| Playlist | Songs in order; you can skip to track 3 (index 2) |
| Student roster | Names stored in enrollment order |
| Hotel floors | Room 0, Room 1, Room 2 ... (zero-based!) |
// A shopping list as an array
const shoppingList = ["Milk", "Eggs", "Bread", "Butter"];
// A playlist
const playlist = ["Song A", "Song B", "Song C"];
// A student roster
const students = ["Alice", "Bob", "Charlie", "Diana"];
Arrays are one of the most frequently used data structures in JavaScript. Almost every real application -- from to-do apps to social media feeds -- stores collections of data in arrays.
2. Array literal syntax
The array literal [] is the most common and recommended way to create arrays.
// Empty array
const empty = [];
// Numbers
const scores = [95, 82, 74, 100, 88];
// Strings
const colors = ["red", "green", "blue"];
// Booleans
const flags = [true, false, true, true];
// Mixed types (valid, but usually not recommended)
const mixed = [42, "hello", true, null, undefined];
Why prefer literals? They are concise, readable, and free of the new Array() quirks discussed next.
// Literal -- always predictable
const nums = [1, 2, 3];
// Nested arrays (arrays inside arrays)
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// Trailing comma is allowed (helpful for clean diffs in version control)
const fruits = [
"apple",
"banana",
"cherry", // <-- trailing comma is fine
];
3. The new Array() constructor (and its quirks)
You can also create arrays with the Array constructor, but beware of its inconsistent behavior.
// Multiple arguments -- creates array WITH those elements
const a = new Array(1, 2, 3);
console.log(a); // [1, 2, 3]
console.log(a.length); // 3
// Single NUMBER argument -- creates array with that LENGTH (not that element!)
const b = new Array(3);
console.log(b); // [ <3 empty items> ] (sparse array!)
console.log(b.length); // 3
console.log(b[0]); // undefined
// Single STRING argument -- creates array with that element
const c = new Array("hello");
console.log(c); // ["hello"]
console.log(c.length); // 1
The quirk summarized:
| Call | Result | Why |
|---|---|---|
new Array(3) | [ , , ] -- 3 empty slots | Single number = length |
new Array("3") | ["3"] -- one element | Single non-number = element |
new Array(1, 2, 3) | [1, 2, 3] -- three elements | Multiple args = elements |
new Array(-1) | RangeError | Negative length is invalid |
// This is confusing -- avoid it
const confusing = new Array(5); // 5 empty slots, NOT [5]
// This is clear
const clear = [5]; // [5] -- one element, the number 5
Rule of thumb: Always use array literals []. Reserve new Array() only if you specifically need to pre-allocate a length (rare).
4. Array.of() -- fixing the constructor quirk
Array.of() was introduced in ES6 to eliminate the single-number ambiguity of new Array().
// Array.of() ALWAYS treats arguments as elements
Array.of(3); // [3] -- one element: the number 3
Array.of(1, 2, 3); // [1, 2, 3]
Array.of("a", "b"); // ["a", "b"]
Array.of(undefined); // [undefined]
// Compare with new Array()
new Array(3); // [ , , ] -- 3 empty slots!
Array.of(3); // [3] -- single element 3
When to use it: When you are programmatically creating an array from a variable and want predictable behavior.
function createSingleItemArray(value) {
// SAFE: always creates [value], even if value is a number
return Array.of(value);
}
createSingleItemArray(5); // [5]
createSingleItemArray("hello"); // ["hello"]
5. Array.from() -- creating arrays from iterables and array-like objects
Array.from() creates a new array from anything iterable (strings, Sets, Maps, NodeLists) or array-like (objects with a length property and indexed elements).
// From a string (each character becomes an element)
Array.from("hello"); // ["h", "e", "l", "l", "o"]
// From a Set (removes duplicates)
const mySet = new Set([1, 2, 2, 3, 3, 3]);
Array.from(mySet); // [1, 2, 3]
// From an arguments object (array-like)
function example() {
const args = Array.from(arguments);
console.log(args); // real array now
}
example(10, 20, 30); // [10, 20, 30]
// From a NodeList (DOM)
// const divs = Array.from(document.querySelectorAll("div"));
With a mapping function (second argument):
// Create and transform in one step
Array.from([1, 2, 3], x => x * 2); // [2, 4, 6]
// Create array of length n filled with values
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]
Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]
Array.from({ length: 3 }, () => 0); // [0, 0, 0]
// Generate a range
Array.from({ length: 10 }, (_, i) => i * 10); // [0, 10, 20, ..., 90]
Comparison of creation methods:
| Method | Best for |
|---|---|
[1, 2, 3] | Known values at write time |
Array.of(x) | Single value, avoiding new Array() quirk |
Array.from(iter) | Converting iterables/array-likes to real arrays |
Array.from({length: n}, fn) | Generating sequences programmatically |
new Array(n).fill(v) | Creating array of n identical values |
6. Arrays can hold mixed types (but should they?)
JavaScript arrays have no type restriction -- a single array can hold numbers, strings, booleans, objects, other arrays, functions, and even undefined or null.
const mixed = [
42, // number
"hello", // string
true, // boolean
null, // null
undefined, // undefined
{ name: "Alice" }, // object
[1, 2, 3], // nested array
function() { return 1; } // function
];
console.log(mixed.length); // 8
console.log(typeof mixed[0]); // "number"
console.log(typeof mixed[1]); // "string"
console.log(typeof mixed[5]); // "object"
Should you mix types? Almost always no. Homogeneous arrays (same type) are:
- Easier to reason about
- Safer to iterate (no type-checking needed)
- Better for performance (engine optimizations)
- Required in TypeScript (
number[]means all numbers)
// Good -- homogeneous
const prices = [9.99, 14.50, 3.25, 22.00];
const names = ["Alice", "Bob", "Charlie"];
// Avoid -- mixed types make code fragile
const bad = [42, "Alice", true, null];
7. Arrays are objects in JavaScript
This surprises many beginners: typeof [] returns "object", not "array".
console.log(typeof []); // "object"
console.log(typeof [1, 2, 3]); // "object"
console.log(typeof "hello"); // "string" (primitive)
console.log(typeof 42); // "number" (primitive)
How to properly check if something is an array:
Array.isArray([1, 2, 3]); // true
Array.isArray([]); // true
Array.isArray("hello"); // false
Array.isArray({ length: 3 }); // false
Array.isArray(new Array(3)); // true
// instanceof also works (but fails across frames/realms)
[1, 2, 3] instanceof Array; // true
Always use Array.isArray() -- it is reliable across different execution contexts (iframes, Web Workers).
8. Reference type behavior
Arrays are reference types. A variable holding an array stores a reference (pointer) to the array in memory, not the array itself.
// Primitives -- copied by VALUE
let a = 10;
let b = a; // b gets a COPY of 10
b = 20;
console.log(a); // 10 (unchanged)
// Arrays -- copied by REFERENCE
let arr1 = [1, 2, 3];
let arr2 = arr1; // arr2 points to the SAME array
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4] -- arr1 changed too!
console.log(arr1 === arr2); // true -- same reference
Visualizing references:
arr1 ──┐
├──> [1, 2, 3, 4] (same object in memory)
arr2 ──┘
This affects function arguments too:
function addItem(list, item) {
list.push(item); // modifies the ORIGINAL array
}
const fruits = ["apple", "banana"];
addItem(fruits, "cherry");
console.log(fruits); // ["apple", "banana", "cherry"] -- modified!
Creating a true copy (shallow):
const original = [1, 2, 3];
// Method 1: spread operator
const copy1 = [...original];
// Method 2: Array.from
const copy2 = Array.from(original);
// Method 3: slice
const copy3 = original.slice();
// Now they are independent
copy1.push(4);
console.log(original); // [1, 2, 3] -- unchanged
console.log(copy1); // [1, 2, 3, 4]
Comparing arrays:
// Two different arrays with same contents are NOT equal
[1, 2, 3] === [1, 2, 3]; // false (different references)
[1, 2, 3] == [1, 2, 3]; // false
// Only the same reference equals itself
const x = [1, 2, 3];
const y = x;
x === y; // true (same reference)
9. Const arrays -- the reference is constant, not the contents
const prevents reassignment of the variable, but does not freeze the array contents.
const colors = ["red", "green", "blue"];
// You CAN modify contents
colors.push("yellow"); // OK
colors[0] = "crimson"; // OK
console.log(colors); // ["crimson", "green", "blue", "yellow"]
// You CANNOT reassign the variable
// colors = ["new", "array"]; // TypeError: Assignment to constant variable
If you truly want an immutable array, use Object.freeze():
const frozen = Object.freeze([1, 2, 3]);
frozen.push(4); // TypeError in strict mode; silently fails otherwise
frozen[0] = 99; // TypeError in strict mode; silently fails otherwise
console.log(frozen); // [1, 2, 3]
Note: Object.freeze() is shallow -- nested arrays inside are not frozen.
const nested = Object.freeze([[1, 2], [3, 4]]);
nested[0].push(99); // This WORKS -- inner array is not frozen
console.log(nested); // [[1, 2, 99], [3, 4]]
10. Empty arrays and sparse arrays (holes)
Empty array:
const empty = [];
console.log(empty.length); // 0
console.log(empty[0]); // undefined (no error)
Sparse arrays have "holes" -- indices with no value assigned.
// Created with new Array(n)
const sparse1 = new Array(5);
console.log(sparse1); // [ <5 empty items> ]
console.log(sparse1.length); // 5
console.log(sparse1[0]); // undefined (but slot is EMPTY, not set to undefined)
// Created by setting length
const sparse2 = [1, 2, 3];
sparse2.length = 6;
console.log(sparse2); // [1, 2, 3, <3 empty items>]
// Created by skipping indices
const sparse3 = [];
sparse3[0] = "a";
sparse3[5] = "f";
console.log(sparse3); // ["a", <4 empty items>, "f"]
console.log(sparse3.length); // 6
// Using delete creates a hole
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, <1 empty item>, 3]
console.log(arr.length); // 3 (unchanged!)
Why sparse arrays are problematic:
const sparse = [1, , 3]; // hole at index 1
// Different methods treat holes differently!
sparse.forEach((v, i) => console.log(i, v));
// 0 1
// 2 3
// (index 1 is SKIPPED)
for (let i = 0; i < sparse.length; i++) {
console.log(i, sparse[i]);
}
// 0 1
// 1 undefined (reads as undefined, not skipped)
// 2 3
Best practice: Avoid sparse arrays. Use Array.from() or .fill() to create dense arrays.
// Dense array of 5 zeros
const dense = new Array(5).fill(0); // [0, 0, 0, 0, 0]
// Dense array of indices
const indices = Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]
Key takeaways
- Array literals
[]are the preferred way to create arrays -- clear, concise, and predictable. new Array(n)createsnempty slots, not an array containingn-- avoid it for clarity.Array.of()fixes the single-number quirk;Array.from()converts iterables and array-likes.- Arrays can hold mixed types, but homogeneous arrays are strongly recommended.
typeof []is"object"-- useArray.isArray()to check.- Arrays are reference types -- assigning an array to another variable shares the same data.
constprotects the variable binding, not the array contents. UseObject.freeze()for shallow immutability.- Sparse arrays (holes) cause inconsistent behavior across methods -- prefer dense arrays.
Explain-It Challenge
Explain without notes:
- Why does
new Array(5)behave differently fromnew Array(5, 6)? - You write
const arr1 = [1, 2]; const arr2 = arr1; arr2.push(3);-- what doesarr1contain and why? - A teammate uses
constfor an array and claims "it can't be changed." What is incorrect about that claim? - Why does
typeof []return"object"and how do you reliably check for an array?
Navigation: <- 1.21 Overview . 1.21.b -- Accessing Elements ->