Episode 1 — Fundamentals / 1.21 — Arrays

1.21.d -- Length Property

In one sentence: The length property returns the count of elements (actually one more than the highest index), is writable so setting it can truncate or expand an array, and understanding its behavior with sparse arrays prevents subtle bugs.

Navigation: <- 1.21.c -- Array Operations . 1.21.e -- Basic Iteration ->


1. What arr.length returns

length is not a method -- it is a property. It returns the number of elements in the array.

const fruits = ["apple", "banana", "cherry"];
console.log(fruits.length);  // 3

const empty = [];
console.log(empty.length);   // 0

const numbers = [10, 20, 30, 40, 50];
console.log(numbers.length); // 5

Technically, length is always one greater than the highest numeric index in the array -- this matters for sparse arrays (Section 5).

const arr = [];
arr[99] = "surprise";
console.log(arr.length);  // 100  (not 1!)

2. Length is writable -- truncation

You can set length to a smaller value to remove elements from the end.

const colors = ["red", "green", "blue", "yellow", "purple"];
console.log(colors.length);  // 5

// Truncate to 3 -- removes "yellow" and "purple"
colors.length = 3;
console.log(colors);         // ["red", "green", "blue"]
console.log(colors.length);  // 3

// The removed elements are gone permanently
console.log(colors[3]);      // undefined
console.log(colors[4]);      // undefined

Truncation is destructive and irreversible:

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

data.length = 2;
console.log(data);  // [1, 2] -- elements 3-10 are gone forever

data.length = 10;
console.log(data);  // [1, 2, <8 empty items>] -- cannot recover!

Practical use -- clearing an array:

const items = [1, 2, 3, 4, 5];

// Method 1: set length to 0 (fastest, mutates)
items.length = 0;
console.log(items);  // []

// Method 2: reassign (only works if not const; does not affect other references)
let arr = [1, 2, 3];
arr = [];

// Method 3: splice (mutates, also affects other references)
const shared = [1, 2, 3];
const ref = shared;
shared.splice(0, shared.length);
console.log(ref);  // [] -- ref is also cleared (same reference)

3. Length is writable -- expansion

Setting length to a larger value creates a sparse array with empty slots (holes).

const arr = [1, 2, 3];
console.log(arr.length);  // 3

arr.length = 7;
console.log(arr);          // [1, 2, 3, <4 empty items>]
console.log(arr.length);   // 7
console.log(arr[5]);       // undefined (empty slot)

// The new slots are holes, not undefined values
console.log(4 in arr);     // false (no element at index 4)
console.log(0 in arr);     // true  (element exists at index 0)

Why you should avoid this: Expanded sparse arrays behave inconsistently across iteration methods (see Section 5).


4. Truncation vs expansion summary

ActionWhat happensReversible?
arr.length = smallerElements beyond new length are deletedNo -- data is lost
arr.length = largerEmpty slots (holes) are createdN/A -- no real data added
arr.length = 0Array is completely clearedNo
arr.length = arr.lengthNo effect--
// Demonstration
const demo = ["a", "b", "c", "d", "e"];

// Shrink
demo.length = 3;
console.log(demo);  // ["a", "b", "c"]

// Grow (creates holes)
demo.length = 6;
console.log(demo);  // ["a", "b", "c", <3 empty items>]

// Clear
demo.length = 0;
console.log(demo);  // []

5. length on sparse arrays

Sparse arrays have gaps (holes) between elements. The length property counts the highest index + 1, not the number of actual elements.

const sparse = [];
sparse[0] = "first";
sparse[100] = "last";

console.log(sparse.length);  // 101 (not 2!)

// Count actual elements
let count = 0;
for (const key in sparse) {
  if (sparse.hasOwnProperty(key)) count++;
}
console.log(count);  // 2

// Or use filter to count non-empty
const dense = sparse.filter(() => true);
console.log(dense.length);  // 2

Sparse arrays cause iteration surprises:

const arr = [1, , 3, , 5];  // holes at index 1 and 3

// for loop -- reads holes as undefined
for (let i = 0; i < arr.length; i++) {
  console.log(i, arr[i]);
}
// 0 1
// 1 undefined  (hole read as undefined)
// 2 3
// 3 undefined  (hole read as undefined)
// 4 5

// forEach -- SKIPS holes
arr.forEach((val, i) => console.log(i, val));
// 0 1
// 2 3
// 4 5

// for...of -- reads holes as undefined
for (const val of arr) {
  console.log(val);
}
// 1, undefined, 3, undefined, 5

Best practice: Avoid sparse arrays entirely. Use fill() or Array.from() to create dense arrays.


6. Using length in loops

The length property is essential for for loops:

const items = ["pen", "notebook", "eraser", "ruler"];

// Standard for loop
for (let i = 0; i < items.length; i++) {
  console.log(`Item ${i + 1}: ${items[i]}`);
}
// Item 1: pen
// Item 2: notebook
// Item 3: eraser
// Item 4: ruler

Caching length for performance:

In extremely hot loops, caching length avoids repeated property lookups:

// Standard (fine for most cases -- engines optimize this)
for (let i = 0; i < items.length; i++) { /* ... */ }

// Cached (micro-optimization, rarely needed)
for (let i = 0, len = items.length; i < len; i++) { /* ... */ }

Iterating backwards:

const arr = [10, 20, 30, 40, 50];

for (let i = arr.length - 1; i >= 0; i--) {
  console.log(arr[i]);
}
// 50, 40, 30, 20, 10

Processing pairs of elements:

const coords = [10, 20, 30, 40, 50, 60];

for (let i = 0; i < coords.length; i += 2) {
  console.log(`Point: (${coords[i]}, ${coords[i + 1]})`);
}
// Point: (10, 20)
// Point: (30, 40)
// Point: (50, 60)

7. Checking for an empty array

const items = [];

// Correct: check length
if (items.length === 0) {
  console.log("Array is empty");
}

// Also correct (truthy/falsy)
if (!items.length) {
  console.log("Array is empty");
}

// WRONG: checking the array itself (arrays are always truthy!)
if (items) {
  console.log("This ALWAYS runs -- even for empty arrays!");
}
// [] is truthy because it is an object, not a primitive

// Checking that something IS an array AND is empty
function isEmpty(value) {
  return Array.isArray(value) && value.length === 0;
}

isEmpty([]);       // true
isEmpty([1, 2]);   // false
isEmpty("");       // false (not an array)
isEmpty(null);     // false

Why [] is truthy:

// All objects are truthy in JavaScript, including:
Boolean([]);       // true
Boolean({});       // true
Boolean(0);        // false (primitive)
Boolean("");       // false (primitive)
Boolean(null);     // false
Boolean(undefined); // false

// So never use `if (arr)` to check for empty -- use `if (arr.length > 0)`

8. Common patterns with length

Last index

const arr = [10, 20, 30, 40, 50];
const lastIndex = arr.length - 1;
console.log(arr[lastIndex]);  // 50

Iterating from the end

const tasks = ["wake up", "eat breakfast", "commute", "work", "sleep"];
for (let i = tasks.length - 1; i >= 0; i--) {
  console.log(`${tasks.length - i}. ${tasks[i]}`);
}

Dynamic sizing -- collecting input

const userInputs = [];

function addInput(value) {
  userInputs.push(value);
  console.log(`Inputs so far: ${userInputs.length}`);
}

addInput("Alice");   // Inputs so far: 1
addInput("Bob");     // Inputs so far: 2
addInput("Carol");   // Inputs so far: 3

Limiting array size (ring buffer pattern)

const MAX_LOG = 5;
const log = [];

function addLog(entry) {
  log.push(entry);
  if (log.length > MAX_LOG) {
    log.shift();  // remove oldest entry
  }
}

addLog("event-1");
addLog("event-2");
addLog("event-3");
addLog("event-4");
addLog("event-5");
addLog("event-6");  // "event-1" is removed
console.log(log);   // ["event-2", "event-3", "event-4", "event-5", "event-6"]
console.log(log.length); // 5 (capped)

9. Real examples: validating input arrays

// Validate before processing
function processScores(scores) {
  // Check if it is actually an array
  if (!Array.isArray(scores)) {
    throw new Error("Expected an array of scores");
  }

  // Check if empty
  if (scores.length === 0) {
    return { average: 0, count: 0, message: "No scores provided" };
  }

  // Check minimum required
  if (scores.length < 3) {
    console.warn("Warning: fewer than 3 scores -- average may not be meaningful");
  }

  const sum = scores.reduce((acc, s) => acc + s, 0);
  return {
    average: sum / scores.length,
    count: scores.length,
    highest: Math.max(...scores),
    lowest: Math.min(...scores)
  };
}

console.log(processScores([85, 92, 78, 95, 88]));
// { average: 87.6, count: 5, highest: 95, lowest: 78 }

console.log(processScores([]));
// { average: 0, count: 0, message: "No scores provided" }

Pagination with length:

const allItems = Array.from({ length: 47 }, (_, i) => `Item ${i + 1}`);
const PAGE_SIZE = 10;

const totalPages = Math.ceil(allItems.length / PAGE_SIZE);
console.log(`Total items: ${allItems.length}`);   // 47
console.log(`Page size: ${PAGE_SIZE}`);             // 10
console.log(`Total pages: ${totalPages}`);          // 5

function getPage(page) {
  const start = (page - 1) * PAGE_SIZE;
  const end = start + PAGE_SIZE;
  return allItems.slice(start, end);
}

console.log(getPage(1).length);  // 10
console.log(getPage(5).length);  // 7  (last page has fewer items)

Key takeaways

  1. arr.length returns one more than the highest index, not necessarily the count of actual values in sparse arrays.
  2. Setting length smaller truncates the array -- removed data is permanently lost.
  3. Setting length larger creates empty slots (holes) -- prefer fill() or Array.from() instead.
  4. arr.length = 0 is the fastest way to clear a mutated array.
  5. An empty array [] is truthy -- always check arr.length === 0, never if (!arr).
  6. Cache length in a variable only in performance-critical inner loops.
  7. length is foundational for loops, pagination, validation, and dynamic sizing patterns.

Explain-It Challenge

Explain without notes:

  1. What happens to the data when you set arr.length = 2 on a 5-element array?
  2. Why does [].length return 0 but if ([]) { ... } still executes the block?
  3. You create const a = []; a[999] = 1; -- what is a.length and why?
  4. Describe two ways to completely empty an array while keeping the same reference.

Navigation: <- 1.21.c -- Array Operations . 1.21.e -- Basic Iteration ->