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(), or Array.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:

AnalogyArray equivalent
Shopping listEach item is an element; the list is the array
PlaylistSongs in order; you can skip to track 3 (index 2)
Student rosterNames stored in enrollment order
Hotel floorsRoom 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:

CallResultWhy
new Array(3)[ , , ] -- 3 empty slotsSingle number = length
new Array("3")["3"] -- one elementSingle non-number = element
new Array(1, 2, 3)[1, 2, 3] -- three elementsMultiple args = elements
new Array(-1)RangeErrorNegative 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:

MethodBest 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

  1. Array literals [] are the preferred way to create arrays -- clear, concise, and predictable.
  2. new Array(n) creates n empty slots, not an array containing n -- avoid it for clarity.
  3. Array.of() fixes the single-number quirk; Array.from() converts iterables and array-likes.
  4. Arrays can hold mixed types, but homogeneous arrays are strongly recommended.
  5. typeof [] is "object" -- use Array.isArray() to check.
  6. Arrays are reference types -- assigning an array to another variable shares the same data.
  7. const protects the variable binding, not the array contents. Use Object.freeze() for shallow immutability.
  8. Sparse arrays (holes) cause inconsistent behavior across methods -- prefer dense arrays.

Explain-It Challenge

Explain without notes:

  1. Why does new Array(5) behave differently from new Array(5, 6)?
  2. You write const arr1 = [1, 2]; const arr2 = arr1; arr2.push(3); -- what does arr1 contain and why?
  3. A teammate uses const for an array and claims "it can't be changed." What is incorrect about that claim?
  4. Why does typeof [] return "object" and how do you reliably check for an array?

Navigation: <- 1.21 Overview . 1.21.b -- Accessing Elements ->