Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

JavaScript Indexed Collections Tutorial

Tech 1

Indexed collections are ordered data sets accessed via index values, including standard Array objects and TypedArray objects. An array is an ordered list of values referenced by a name and an index. For example, an array named employeeNames holding employee names indexed by their ID numbers: employeeNames[0] is the 0th employee, employeeNames[1] the 1st, and so on.

JavaScript doesn’t have a dedicated array data type, but you can use the built-in Array object and its methods to work with arrays in your applications. The Array object includes methods for common operations like concatenating, reversing, and sorting arrays, plus a length property for tracking array size and other utility properties. While this guide focuses on standard JavaScript arrays, many of these concepts apply to typed arrays too, as both array types share a large set of common methods. For more details on typed arrays, see the official TypedArray reference documentation.

Creating Arrays

The following statements create equivalent arrays:

const arrayOne = new Array(element0, element1, /* … ,*/ elementN);
const arrayTwo = Array(element0, element1, /* … ,*/ elementN);
const arrayThree = [element0, element1, /* … ,*/ elementN];

Where element0 through elementN are the values for the array elements. When these values are specified, the array is initialized with them as its elements, and the length property is set to the number of arguments. The bracket syntax is called array literal or array initializer, and it’s shorter than other array creation methods, so it’s generally the preferred approach. See the array literal documentation for more details.

To create an array with a specified length but no initial elements, use one of these approaches:

// This method...
const emptyArrOne = new Array(arrayLength);

// ...is equivalent to this one
const emptyArrTwo = Array(arrayLength);

// This also works the same way
const emptyArrThree = [];
emptyArrThree.length = arrayLength;

Note: The arrayLength value must be a number. Otherwise, the constructor will create an array with a single element containing the provided value. Calling arr.length will return the provided value, but the array will have no actual elements, and a for...in loop will not find any enumerable properties on the array.

You can also assign an array to a new object property or an existing object:

const user = {};
// ...
user.teamMembers = ["Mia", "Liam", "Noah"];

// Or inline during object creation
const product = {
  tags: ["electronics", "portable", "wireless"]
};

If you want to create an array with a single numeric element, you must use the array literal syntax. The Array constructor interprets a single numeric argument as the array length, not as an element:

// Creates an array with a single element: the number 42
const singleNumArr = [42];

// Creates an empty array with length 42, no actual elements
const emptyLongArr = Array(42);

// This code is equivalent to the line above
const anotherEmptyLongArr = [];
anotherEmptyLongArr.length = 42;

If the provided argument is not an integer, calling Array(N) will throw a RangeError:

const invalidLengthArr = Array(9.3); // Throws RangeError: Invalid array length

To safely create an array with a single element of any type, use the array literal syntax, or create an empty array and add the element afterward. You can also use the Array.of static method, which always creates an array with the provided arguments as elements:

const preciseArray = Array.of(9.3); // Contains exactly one element: 9.3

Referencing Array Elements

Since array elements are also object properties, you can access them using property access syntax. For example, given this array:

const weatherTypes = ["Wind", "Rain", "Fire"];

You can reference the first element with weatherTypes[0], the second with weatherTypes[1], and so on. Array indices in JavaScript are zero-based.

Note: You can also access array properties using property access syntax, just like with standard objects:

const colorList = ["red", "green", "blue"];
colorList[2]; // Returns "blue"
colorList["length"]; // Returns 3

Populating Arrays

You can populate an array by assigning values to its indices:

const staff = [];
staff[0] = "Casey Jones";
staff[1] = "Phil Lesh";
staff[2] = "August West";

Note: If you use a non-integer value as an array index, JavaScript will create a property on the underlying array object instead of adding an element to the array:

const mixedIndexArr = [];
mixedIndexArr[3.4] = "Oranges";
console.log(mixedIndexArr.length); // Logs 0, since no valid array elements were added
console.log(Object.hasOwn(mixedIndexArr, 3.4)); // Logs true, the property exists

You can also populate an array at creation time:

const greetingArray = new Array("Hello", userScore, 3.14159);
// Or using array literal syntax
const fruitArray = ["Mango", "Apple", "Orange"];

Understanding the length Property

Under the hood, JavaScript stores array elements as standard object properties, with array indices used as the property names. The length property is special: its value is always a positive integer that is one greater than the highest defined index. For example, if cats[30] = ["Dusty"], then cats.length will return 31.

You can assign a value to the length property to modify the array. Setting a value smaller than the current number of elements will truncate the array. Setting length to 0 will empty the array entirely. Setting a value larger than the current length will add empty slots to the array:

const catNames = ["Dusty", "Misty", "Twiggy"];
console.log(catNames.length); // Logs 3

catNames.length = 2;
console.log(catNames); // Logs ["Dusty", "Misty"] — "Twiggy" has been removed

catNames.length = 0;
console.log(catNames); // Logs [], the array is now empty

catNames.length = 3;
console.log(catNames); // Logs [ <3 empty items> ]

Traversing Arrays

A common task is to iterate over an array and process each element. The simplest approach uses a standard for loop:

const colorList = ["red", "green", "blue"];
for (let i = 0; i < colorList.length; i++) {
  console.log(colorList[i]);
}

If you know your array contains no falsy values (for example, a list of DOM nodes returned by document.getElementsByTagName), you can use a more concise loop:

const pageDivs = document.getElementsByTagName("div");
for (let i = 0, currentDiv; (currentDiv = pageDivs[i]); i++) {
  // Process the current div element
}

This avoids the overhead of checking the array length on each iteration and ensures the currentDiv variable is updated to the current element each loop cycle.

The forEach() method provides another clean way to iterate over array elements:

const colorList = ["red", "green", "blue"];
colorList.forEach((color) => console.log(color));
// Logs:
// red
// green
// blue

The function passed to forEach() runs once for each element in the array, with the element value passed as an argument. Empty slots in sparse arrays are skipped by forEach(), but elements explicitly set to undefined are not:

const sparseArray = ["first", "second", , "fourth"];

sparseArray.forEach((element) => {
  console.log(element);
});
// Logs:
// first
// second
// fourth

if (sparseArray[2] === undefined) {
  console.log("sparseArray[2] is undefined"); // Logs true
}

const nonSparseArray = ["first", "second", undefined, "fourth"];

nonSparseArray.forEach((element) => {
  console.log(element);
});
// Logs:
// first
// second
// undefined
// fourth

It’s not recommended to use a for...in loop to iterate over JavaScript arrays, as this loop enumerates all enumerable properties, not just the array indices.

Array Methods

The Array object includes the following useful methods:

concat()

Joins two or more arrays and returns a new array:

let numArray = ["1", "2", "3"];
numArray = numArray.concat("a", "b", "c");
// numArray is now ["1", "2", "3", "a", "b", "c"]

join()

Combines all array elements into a single string, with an optional separator:

const weatherArray = ["Wind", "Rain", "Fire"];
const joinedList = weatherArray.join(" - ");
// joinedList is "Wind - Rain - Fire"

push()

Adds one or more elements to the end of an array and returns the new length of the array:

const numArray = ["1", "2"];
numArray.push("3");
// numArray is now ["1", "2", "3"]

pop()

Removes the last element from an array and returns that element:

const numArray = ["1", "2", "3"];
const lastElement = numArray.pop();
// numArray is now ["1", "2"], lastElement is "3"

shift()

Removes the first element from an array and returns that element:

const numArray = ["1", "2", "3"];
const firstElement = numArray.shift();
// numArray is now ["2", "3"], firstElement is "1"

unshift()

Adds one or more elements to the start of an array and returns the new length:

const numArray = ["1", "2", "3"];
numArray.unshift("4", "5");
// numArray is now ["4", "5", "1", "2", "3"]

slice()

Extracts a section of an array and returns a new array without modifying the original:

let charArray = ["a", "b", "c", "d", "e"];
charArray = charArray.slice(1, 4);
// Returns ["b", "c", "d"] — starts at index 1 and ends before index 4

at()

Returns the element at the specified index, and supports negative indices to access elements from the end of the array. Returns undefined if the index is out of bounds:

const charArray = ["a", "b", "c", "d", "e"];
charArray.at(-2); // Returns "d", the second-to-last element

splice()

Removes elements from an array and optionally replaces them with new elements. Returns an array of the removed elements:

let numArray = ["1", "2", "3", "4", "5"];
numArray.splice(1, 3, "a", "b", "c", "d");
// numArray is now ["1", "a", "b", "c", "d", "5"]
// This code removes 3 elements starting at index 1, then inserts the new elements at that position

reverse()

Reverses the order of elements in the array in place, and returns a reference to the modified array:

const numArray = ["1", "2", "3"];
numArray.reverse();
// numArray is now ["3", "2", "1"]

flat()

Returns a new array with all sub-array elements concatenated recursively up to the specified depth:

let nestedArray = [1, 2, [3, 4]];
nestedArray = nestedArray.flat();
// nestedArray is now [1, 2, 3, 4]

sort()

Sorts the elements of an array in place and returns a reference to the modified array. By default, it sorts elements as strings:

const fruitArray = ["Banana", "Orange", "Apple", "Mango"];
fruitArray.sort();
// fruitArray is now ["Apple", "Banana", "Mango", "Orange"]

You can pass a callback function to sort() to define custom comparison logic. The callback receives two arguments representing the two elements being compared, and returns a negative number, zero, or a positive number depending on whether the first element should come before, match the position of, or after the second element:

const sortByLastCharacter = (a, b) => {
  if (a[a.length - 1] < b[b.length - 1]) {
    return -1;
  } else if (a[a.length - 1] > b[b.length - 1]) {
    return 1;
  }
  return 0;
};
fruitArray.sort(sortByLastCharacter);

indexOf()

Searches the array for a specified element and returns the first matching index, or -1 if no match is found. You can pass an optional starting index to begin the search:

const alphaArray = ["a", "b", "a", "b", "a"];
console.log(alphaArray.indexOf("b")); // Logs 1
console.log(alphaArray.indexOf("b", 2)); // Logs 3, starts searching from index 2
console.log(alphaArray.indexOf("z")); // Logs -1, no match found

lastIndexOf()

Searches the array backwards for a specified element and returns the last matching index, or -1 if no match is found. You can pass an optional starting index to begin the backward search:

const alphaArray = ["a", "b", "c", "d", "a", "b"];
console.log(alphaArray.lastIndexOf("b")); // Logs 5
console.log(alphaArray.lastIndexOf("b", 4)); // Logs 1, searches backwards from index 4
console.log(alphaArray.lastIndexOf("z")); // Logs -1

forEach()

Executes a provided function once for each array element, and returns undefined:

const alphaArray = ["a", "b", "c"];
alphaArray.forEach((element) => {
  console.log(element);
});
// Logs:
// a
// b
// c

Methods like forEach() that accept a callback function are called iterative methods, as they traverse the entire array in some fashion. Most of these methods accept an optional second argument thisArg, which will be used as the value of the this keyword inside the callback function. If no thisArg is provided, this will be undefined in strict mode, or the global object in non-strict mode.

Note: The sort() method is not considered an iterative method, as its callback only handles comparison logic and is not called in a fixed order for each element. It also does not accept a thisArg parameter.

map()

Creates a new array populated with the results of calling a provided function on every element in the calling array:

const lowerCaseArray = ["a", "b", "c"];
const upperCaseArray = lowerCaseArray.map((item) => item.toUpperCase());
// upperCaseArray is ["A", "B", "C"]

flatMap()

First maps each element using a mapping function, then flattens the result into a new array with a depth of 1:

const lowerCaseArray = ["a", "b", "c"];
const flatMappedArray = lowerCaseArray.flatMap((item) => [item.toUpperCase(), item.toLowerCase()]);
// flatMappedArray is ["A", "a", "B", "b", "C", "c"]

filter()

Creates a new array with all elements that pass the test implemented by the provided callback function:

const mixedTypeArray = ["a", 10, "b", 20, "c", 30];
const numberOnlyArray = mixedTypeArray.filter((item) => typeof item === "number");
// numberOnlyArray is [10, 20, 30]

find()

Returns the first element in the array that satisfies the provided testing function, or undefined if no matching element is found:

const mixedTypeArray = ["a", 10, "b", 20, "c", 30];
const firstNumber = mixedTypeArray.find((item) => typeof item === "number");
// firstNumber is 10

findLast()

Returns the last element in the array that satisfies the provided testing function, or undefined if no matching element is found:

const mixedTypeArray = ["a", 10, "b", 20, "c", 30];
const lastNumber = mixedTypeArray.findLast((item) => typeof item === "number");
// lastNumber is 30

findIndex()

Returns the index of the first element in the array that satisfies the provided testing function, or -1 if no matching element is found:

const mixedTypeArray = ["a", 10, "b", 20, "c", 30];
const firstNumberIndex = mixedTypeArray.findIndex((item) => typeof item === "number");
// firstNumberIndex is 1

findLastIndex()

Returns the index of the last element in the array that satisfies the provided testing function, or -1 if no matching element is found:

const mixedTypeArray = ["a", 10, "b", 20, "c", 30];
const lastNumberIndex = mixedTypeArray.findLastIndex((item) => typeof item === "number");
// lastNumberIndex is 5

every()

Tests whether all elements in the array pass the test implemented by the provided function. Returns true if all elements pass, otherwise false:

const isNumber = (value) => typeof value === "number";
const allNumbers = [1, 2, 3];
console.log(allNumbers.every(isNumber)); // Logs true
const mixedNumbers = [1, "2", 3];
console.log(mixedNumbers.every(isNumber)); // Logs false

some()

Tests whether atleast one element in the array passes the test implemented by the provided function. Returns true if any element passes, otherwise false:

const isNumber = (value) => typeof value === "number";
const allNumbers = [1, 2, 3];
console.log(allNumbers.some(isNumber)); // Logs true
const mixedNumbers = [1, "2", 3];
console.log(mixedNumbers.some(isNumber)); // Logs true
const allStrings = ["1", "2", "3"];
console.log(allStrings.some(isNumber)); // Logs false

reduce()

Applies a callback function against an accumulator and each element in the array (from left to right) to reduce it to a single value. You can provide an optional initial value for the accumulator:

const numberList = [10, 20, 30];
const totalSum = numberList.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
// totalSum is 60

reduceRight()

Works identically to reduce(), but traverses the array from right to left.

The reduce() and reduceRight() methods are the most versatile iterative array methods, and should be used for scenarios where you need to recursively process pairs of array elements to produce a single final result.

Sparse Arrays

Arrays can contain empty slots, which are different from slots filled with the undefined value. Empty slots can be created in several ways:

// Using the Array constructor with a single numeric argument:
const sparseOne = Array(5); // [ <5 empty items> ]

// Using consecutive commas in an array literal:
const sparseTwo = [1, 2, , , 5]; // [1, 2, <2 empty items>, 5]

// Assigning a value to an index greater than the current array length:
const sparseThree = [1, 2];
sparseThree[4] = 5; // [1, 2, <2 empty items>, 5]

// Increasing the array length manually:
const sparseFour = [1, 2];
sparseFour.length = 5; // [1, 2, <3 empty items>]

// Deleting an existing array element:
const sparseFive = [1, 2, 3, 4, 5];
delete sparseFive[2]; // [1, 2, <1 empty item>, 4, 5]

In some operations, empty slots behave as if they were filled with undefined:

const sparseArray = [1, 2, , , 5];

// Accessing an empty slot returns undefined
console.log(sparseArray[2]); // Logs undefined

// Using for...of loop
for (const value of sparseArray) {
  console.log(value);
}
// Logs: 1, 2, undefined, undefined, 5

// Using spread operator
const copiedArray = [...sparseArray]; // [1, 2, undefined, undefined, 5]

In other operations, particularly array iterative methods, empty slots are skipped entirely:

const mappedArray = sparseArray.map((i) => i + 1); // [2, 3, <2 empty items>, 6]
sparseArray.forEach((i) => console.log(i)); // Logs 1, 2, 5
const filteredArray = sparseArray.filter(() => true); // [1, 2, 5]
const hasFalsyValue = sparseArray.some((k) => !k); // Logs false

// Property enumeration
const existingIndices = Object.keys(sparseArray); // ["0", "1", "4"]
for (const index in sparseArray) {
  console.log(index);
}
// Logs: "0", "1", "4"

// Object spread converts sparse array to object with only defined indices
const spreadObject = { ...sparseArray }; // { "0": 1, "1": 2, "4": 5 }

For a full list of how array methods behave with sparse arrays, see the official Array reference documentation.

Multidimensional Arrays

Arrays can be nested, meaning an array can be an element of another array. This lets you create multidimensional arrays in JavaScript. The following example creates a 4x4 two-dimensional array:

const twoDArray = new Array(4);
for (let row = 0; row < 4; row++) {
  twoDArray[row] = new Array(4);
  for (let col = 0; col < 4; col++) {
    twoDArray[row][col] = `[${row},${col}]`;
  }
}

The resulting array has the following row data:

Row 0: [0,0] [0,1] [0,2] [0,3]
Row 1: [1,0] [1,1] [1,2] [1,3]
Row 2: [2,0] [2,1] [2,2] [2,3]
Row 3: [3,0] [3,1] [3,2] [3,3]

Using Arrays to Store Additional Properties

Arrays can also be used like standard objects to store additional properties:

const sampleArray = [1, 2, 3];
sampleArray.customProperty = "test value";
console.log(sampleArray.customProperty); // Logs "test value"

For example, when an array is returned as the result of a string or regular expression match operation, the array includes additional properties with matching information. The return values of RegExp.prototype.exec(), String.prototype.match(), and String.prototype.split() are all arrays with extra properties. For more information on using arrays with regular expressions, see the regular expressions documentation.

Using Array-like Objects

Some JavaScript objects, such as the NodeList returned by document.getElementsByTagName() or the arguments object inside a function, behave similarly to arrays but do not share all of the built-in array methods. The arguments object has a length property but does not include methods like forEach():

function printArguments() {
  arguments.forEach((item) => {
    console.log(item);
  }); // Throws TypeError: arguments.forEach is not a function
}

You can indirectly call array methods on array-like objects using Function.prototype.call():

function printArguments() {
  Array.prototype.forEach.call(arguments, (item) => {
    console.log(item);
  });
}

You can also use array prototype methods on strings, since strings provide sequential access to their characters in a way similar to arrays:

Array.prototype.forEach.call("a test string", (character) => {
  console.log(character);
});

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.