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.
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
- Use for Index Iteration: When you need indices rather than values
- Convert When Needed: Convert to array for multiple iterations
- Consider Performance: Use traditional loops for performance-critical code
- Handle Sparse Arrays: Be aware that keys() includes empty slots
- 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.