JavaScript Basics

JavaScript Array keys() Method: Iterate Over Array Indices

Master the JavaScript Array keys() method to iterate over array indices. Learn how to work with iterators, convert to arrays, and handle sparse arrays.

By JavaScript Document Team
arraysmethodsbasicsiteratorses6

The keys() method returns a new Array Iterator object that contains the keys (indices) for each index in the array, including empty slots in sparse arrays. It's part of the iterator protocol introduced in ES6.

Understanding Array keys()

The keys() method provides a way to iterate over array indices without needing to access the actual values. It returns an iterator that yields the numeric indices of the array in order.

Syntax

array.keys();

Parameters

The keys() method takes no parameters.

Return Value

A new Array Iterator object that yields array indices.

Basic Usage

Simple Iteration

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

// Get the iterator
const iterator = fruits.keys();

// Iterate manually using next()
console.log(iterator.next()); // { value: 0, done: false }
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Using for...of Loop

const colors = ['red', 'green', 'blue', 'yellow'];

// Iterate over indices using for...of
for (const index of colors.keys()) {
  console.log(index); // 0, 1, 2, 3
}

// Access both index and value
for (const index of colors.keys()) {
  console.log(`Index ${index}: ${colors[index]}`);
}
// Index 0: red
// Index 1: green
// Index 2: blue
// Index 3: yellow

Working with Iterators

Converting to Array

const arr = ['a', 'b', 'c', 'd', 'e'];

// Convert iterator to array using spread operator
const indices = [...arr.keys()];
console.log(indices); // [0, 1, 2, 3, 4]

// Using Array.from()
const indicesArray = Array.from(arr.keys());
console.log(indicesArray); // [0, 1, 2, 3, 4]

// Create a range of numbers
const range = [...Array(5).keys()];
console.log(range); // [0, 1, 2, 3, 4]

Iterator Protocol

const numbers = [10, 20, 30];
const iter = numbers.keys();

// Iterators implement the iterator protocol
console.log(typeof iter.next); // 'function'
console.log(Symbol.iterator in iter); // true

// Manually consuming the iterator
let result = iter.next();
while (!result.done) {
  console.log(`Index: ${result.value}`);
  result = iter.next();
}

Practical Examples

Creating Index-Based Operations

class ArrayHelper {
  static getEvenIndices(arr) {
    return [...arr.keys()].filter((index) => index % 2 === 0);
  }

  static getOddIndices(arr) {
    return [...arr.keys()].filter((index) => index % 2 !== 0);
  }

  static reverseIndices(arr) {
    return [...arr.keys()].reverse();
  }

  static getRandomIndex(arr) {
    const indices = [...arr.keys()];
    return indices[Math.floor(Math.random() * indices.length)];
  }
}

const data = ['a', 'b', 'c', 'd', 'e', 'f'];

console.log(ArrayHelper.getEvenIndices(data)); // [0, 2, 4]
console.log(ArrayHelper.getOddIndices(data)); // [1, 3, 5]
console.log(ArrayHelper.reverseIndices(data)); // [5, 4, 3, 2, 1, 0]

Pagination Helper

class Paginator {
  constructor(items, pageSize = 10) {
    this.items = items;
    this.pageSize = pageSize;
  }

  getPageIndices(pageNumber) {
    const start = pageNumber * this.pageSize;
    const end = Math.min(start + this.pageSize, this.items.length);

    if (start >= this.items.length) {
      return [];
    }

    // Get indices for the current page
    return [...this.items.keys()].slice(start, end);
  }

  *pages() {
    const totalPages = Math.ceil(this.items.length / this.pageSize);

    for (let page = 0; page < totalPages; page++) {
      const indices = this.getPageIndices(page);
      yield {
        page,
        indices,
        items: indices.map((i) => this.items[i]),
      };
    }
  }
}

const items = Array.from({ length: 25 }, (_, i) => `Item ${i + 1}`);
const paginator = new Paginator(items, 5);

// Get indices for specific page
console.log(paginator.getPageIndices(0)); // [0, 1, 2, 3, 4]
console.log(paginator.getPageIndices(1)); // [5, 6, 7, 8, 9]

// Iterate through all pages
for (const pageData of paginator.pages()) {
  console.log(`Page ${pageData.page}: Indices ${pageData.indices}`);
}

Sparse Array Handling

// keys() includes indices for empty slots
const sparse = [1, , , 4, , 6];
console.log(sparse.length); // 6

// keys() returns all indices including empty slots
const allKeys = [...sparse.keys()];
console.log(allKeys); // [0, 1, 2, 3, 4, 5]

// Compare with actual defined indices
const definedIndices = [];
sparse.forEach((value, index) => {
  definedIndices.push(index);
});
console.log(definedIndices); // [0, 3, 5]

// Function to get only defined indices
function getDefinedIndices(arr) {
  const indices = [];
  for (const index of arr.keys()) {
    if (index in arr) {
      indices.push(index);
    }
  }
  return indices;
}

console.log(getDefinedIndices(sparse)); // [0, 3, 5]

Advanced Patterns

Custom Range Generator

class RangeGenerator {
  static range(start, end, step = 1) {
    const length = Math.ceil((end - start) / step);
    return [...Array(length).keys()].map((i) => start + i * step);
  }

  static indexRange(arr, start = 0, end = arr.length) {
    return [...arr.keys()].slice(start, end);
  }

  static everyNth(arr, n, offset = 0) {
    return [...arr.keys()].filter((i) => (i - offset) % n === 0);
  }
}

console.log(RangeGenerator.range(1, 10, 2)); // [1, 3, 5, 7, 9]
console.log(RangeGenerator.range(0, 5)); // [0, 1, 2, 3, 4]

const array = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
console.log(RangeGenerator.indexRange(array, 2, 6)); // [2, 3, 4, 5]
console.log(RangeGenerator.everyNth(array, 3)); // [0, 3, 6]
console.log(RangeGenerator.everyNth(array, 2, 1)); // [1, 3, 5, 7]

Matrix Index Generation

class MatrixIndexer {
  constructor(rows, cols) {
    this.rows = rows;
    this.cols = cols;
  }

  // Get all indices as [row, col] pairs
  *allIndices() {
    for (const row of [...Array(this.rows).keys()]) {
      for (const col of [...Array(this.cols).keys()]) {
        yield [row, col];
      }
    }
  }

  // Get diagonal indices
  *diagonalIndices() {
    const min = Math.min(this.rows, this.cols);
    for (const i of [...Array(min).keys()]) {
      yield [i, i];
    }
  }

  // Get row indices
  rowIndices(rowNum) {
    return [...Array(this.cols).keys()].map((col) => [rowNum, col]);
  }

  // Get column indices
  colIndices(colNum) {
    return [...Array(this.rows).keys()].map((row) => [row, colNum]);
  }
}

const matrix = new MatrixIndexer(3, 4);

console.log([...matrix.allIndices()]);
// [[0,0], [0,1], [0,2], [0,3], [1,0], [1,1], [1,2], [1,3], [2,0], [2,1], [2,2], [2,3]]

console.log([...matrix.diagonalIndices()]);
// [[0,0], [1,1], [2,2]]

console.log(matrix.rowIndices(1));
// [[1,0], [1,1], [1,2], [1,3]]

Shuffling Indices

function* shuffledIndices(arr) {
  const indices = [...arr.keys()];
  const shuffled = [...indices];

  // Fisher-Yates shuffle
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }

  yield* shuffled;
}

const items = ['A', 'B', 'C', 'D', 'E'];
const shuffled = [...shuffledIndices(items)];
console.log(shuffled); // e.g., [2, 0, 4, 1, 3]

// Access items in shuffled order
for (const index of shuffledIndices(items)) {
  console.log(items[index]);
}

Comparison with Other Methods

keys() vs Object.keys()

const arr = ['a', 'b', 'c'];
arr.customProp = 'test';

// Array.keys() returns numeric indices
console.log([...arr.keys()]); // [0, 1, 2]

// Object.keys() returns all enumerable properties
console.log(Object.keys(arr)); // ['0', '1', '2', 'customProp']

// For sparse arrays
const sparse = [1, , 3];
console.log([...sparse.keys()]); // [0, 1, 2]
console.log(Object.keys(sparse)); // ['0', '2'] (only defined indices)

keys() vs entries() vs values()

const colors = ['red', 'green', 'blue'];

// keys() - returns indices
for (const key of colors.keys()) {
  console.log(key); // 0, 1, 2
}

// values() - returns values
for (const value of colors.values()) {
  console.log(value); // 'red', 'green', 'blue'
}

// entries() - returns [index, value] pairs
for (const [index, value] of colors.entries()) {
  console.log(`${index}: ${value}`); // '0: red', '1: green', '2: blue'
}

Performance Considerations

// Performance comparison for index generation
const largeArray = Array(100000).fill(0);

// Using keys() iterator
console.time('keys iterator');
for (const index of largeArray.keys()) {
  // Process index
}
console.timeEnd('keys iterator');

// Using traditional for loop
console.time('for loop');
for (let i = 0; i < largeArray.length; i++) {
  // Process index
}
console.timeEnd('for loop');

// Converting to array
console.time('keys to array');
const indices = [...largeArray.keys()];
console.timeEnd('keys to array');

// Traditional loops are generally faster for simple index access

Practical Utilities

Index-Based Filtering

function filterByIndices(arr, predicate) {
  const result = [];
  for (const index of arr.keys()) {
    if (predicate(index)) {
      result.push(arr[index]);
    }
  }
  return result;
}

const data = ['a', 'b', 'c', 'd', 'e', 'f'];

// Get elements at even indices
const evenIndexed = filterByIndices(data, (i) => i % 2 === 0);
console.log(evenIndexed); // ['a', 'c', 'e']

// Get first and last n elements
const firstAndLast = filterByIndices(
  data,
  (i) => i < 2 || i >= data.length - 2
);
console.log(firstAndLast); // ['a', 'b', 'e', 'f']

Creating Index Maps

function createIndexMap(arr, keyFn) {
  const map = new Map();

  for (const index of arr.keys()) {
    const key = keyFn(arr[index], index);
    if (!map.has(key)) {
      map.set(key, []);
    }
    map.get(key).push(index);
  }

  return map;
}

const words = ['apple', 'banana', 'apricot', 'berry', 'avocado'];

// Group indices by first letter
const indexByFirstLetter = createIndexMap(words, (word) => word[0]);
console.log(indexByFirstLetter);
// Map { 'a' => [0, 2, 4], 'b' => [1, 3] }

// Group indices by length
const indexByLength = createIndexMap(words, (word) => word.length);
console.log(indexByLength);
// Map { 5 => [0, 3], 6 => [1], 7 => [2, 4] }

Edge Cases

Empty Arrays

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

Arrays with Custom Properties

const arr = [1, 2, 3];
arr.customMethod = function () {
  return 'custom';
};
arr[-1] = 'negative index';
arr['foo'] = 'bar';

// keys() only returns numeric indices
console.log([...arr.keys()]); // [0, 1, 2]

// Object.keys includes all enumerable properties
console.log(Object.keys(arr)); // ['0', '1', '2', '-1', 'foo', 'customMethod']

Best Practices

  1. Use for Index Iteration: When you need indices rather than values
  2. Convert When Needed: Convert to array for multiple iterations
  3. Consider Performance: Use traditional loops for performance-critical code
  4. Handle Sparse Arrays: Be aware that keys() includes empty slots
  5. Combine with Other Methods: Use with entries() or values() for complete iteration patterns

Conclusion

The keys() method provides a clean, iterator-based approach to working with array indices. While it may seem simple, it's particularly useful when you need to work with indices independently of values, create ranges, or implement index-based algorithms. Understanding how to use keys() effectively, especially in combination with other iterator methods, can lead to more elegant and functional programming patterns in JavaScript.