JavaScript Essentials

Arrays: Creating & Accessing Elements

45 min Lesson 13 of 60

What Are Arrays?

An array is one of the most fundamental and frequently used data structures in JavaScript. At its core, an array is an ordered collection of values stored under a single variable name. Instead of creating dozens of separate variables to hold related pieces of data, you can group them all inside one array. Each value in the array is called an element, and each element has a numeric position called an index. Arrays in JavaScript are zero-indexed, which means the first element sits at index 0, the second at index 1, and so on.

Arrays are incredibly versatile. They can hold any type of data -- strings, numbers, booleans, objects, other arrays, and even functions. A single array can contain a mix of different types, though in practice it is usually better to keep arrays homogeneous (containing the same type) for clarity and maintainability. Arrays are used everywhere in real-world programming: lists of users, collections of products, rows of database results, series of coordinates, queues of tasks, and much more.

Understanding arrays deeply is essential because they form the backbone of data manipulation in JavaScript. Almost every application you build will involve creating arrays, reading elements from them, transforming them, filtering them, and combining them. In this lesson we will cover every way to create arrays, how to access and modify their elements, and several powerful patterns that professional developers use daily.

Creating Arrays with Array Literals

The most common and recommended way to create an array in JavaScript is with the array literal syntax. You use square brackets [] and separate each element with a comma. This approach is clean, concise, and easy to read.

Example: Creating Arrays with Literal Syntax

// An array of strings
const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry'];

// An array of numbers
const scores = [95, 87, 72, 100, 64, 88];

// An array of booleans
const flags = [true, false, true, true, false];

// An empty array (you will add elements later)
const emptyList = [];

// An array with mixed types (valid but not always recommended)
const mixed = ['hello', 42, true, null, undefined, { name: 'Ali' }];

console.log(fruits);   // ['apple', 'banana', 'cherry', 'date', 'elderberry']
console.log(scores);   // [95, 87, 72, 100, 64, 88]
console.log(emptyList); // []

The literal syntax is preferred over the Array constructor because it is shorter, clearer, and avoids subtle bugs that the constructor can introduce. You should use array literals in almost every situation.

Creating Arrays with the Array Constructor

JavaScript also provides the Array constructor function to create arrays. You call it with the new keyword (though new is optional). While this approach works, it has a well-known quirk that makes it less predictable than literals.

Example: The Array Constructor

// Creating an array with elements
const colors = new Array('red', 'green', 'blue');
console.log(colors); // ['red', 'green', 'blue']

// The dangerous quirk: passing a single number
const fiveSlots = new Array(5);
console.log(fiveSlots);        // [empty x 5]
console.log(fiveSlots.length); // 5
// This creates an array with 5 empty slots, NOT an array containing the number 5!

// To create an array containing the number 5, use literals:
const containsFive = [5];
console.log(containsFive); // [5]

// Without the 'new' keyword (works the same way)
const animals = Array('cat', 'dog', 'bird');
console.log(animals); // ['cat', 'dog', 'bird']
Common Mistake: Using new Array(5) when you intend to create an array containing the number 5. This instead creates an array with 5 empty slots. Always use the literal syntax [5] to avoid this confusion. The constructor quirk has caused countless bugs in production code.

Creating Arrays with Array.of()

The Array.of() static method was introduced in ES6 to solve the ambiguity problem of the Array constructor. It always creates an array containing the arguments you pass, regardless of the number or type of arguments. If you pass a single number, it creates an array containing that number, not an array with that many empty slots.

Example: Array.of() Solves the Constructor Quirk

// Array.of() always creates an array with the given elements
const singleNumber = Array.of(5);
console.log(singleNumber);        // [5]
console.log(singleNumber.length); // 1

// Compare with the constructor
const constructorResult = new Array(5);
console.log(constructorResult);        // [empty x 5]
console.log(constructorResult.length); // 5

// Multiple arguments work as expected
const values = Array.of(1, 2, 3, 4, 5);
console.log(values); // [1, 2, 3, 4, 5]

// Mixed types
const mixedOf = Array.of('hello', 42, true);
console.log(mixedOf); // ['hello', 42, true]

// Empty array
const emptyOf = Array.of();
console.log(emptyOf); // []
Tip: Use Array.of() when you need to create an array programmatically from a variable number of arguments and want predictable behavior. In most everyday coding, the literal syntax [] is still the simplest choice.

Creating Arrays with Array.from()

Array.from() is one of the most powerful array creation methods in JavaScript. It creates a new array from any iterable or array-like object. Array-like objects are objects that have a length property and indexed elements but lack array methods like push or map. Common examples include strings, NodeList objects from the DOM, the arguments object inside functions, and Set and Map objects.

Example: Array.from() with Various Sources

// From a string (each character becomes an element)
const letters = Array.from('Hello');
console.log(letters); // ['H', 'e', 'l', 'l', 'o']

// From a Set (removes duplicates automatically)
const uniqueNumbers = new Set([1, 2, 2, 3, 3, 3]);
const uniqueArray = Array.from(uniqueNumbers);
console.log(uniqueArray); // [1, 2, 3]

// From a Map
const userMap = new Map([['name', 'Ali'], ['age', 30]]);
const mapArray = Array.from(userMap);
console.log(mapArray); // [['name', 'Ali'], ['age', 30]]

// From an array-like object
const arrayLike = { 0: 'first', 1: 'second', 2: 'third', length: 3 };
const realArray = Array.from(arrayLike);
console.log(realArray); // ['first', 'second', 'third']

Array.from() also accepts an optional second argument -- a mapping function that transforms each element as it is added to the new array. This is extremely useful for generating sequences or transforming data in one step.

Example: Array.from() with a Mapping Function

// Generate an array of numbers from 1 to 10
const oneToTen = Array.from({ length: 10 }, (_, index) => index + 1);
console.log(oneToTen); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// Generate an array of squared values
const squares = Array.from({ length: 5 }, (_, i) => (i + 1) ** 2);
console.log(squares); // [1, 4, 9, 16, 25]

// Convert a string to uppercase letters
const upperLetters = Array.from('hello', char => char.toUpperCase());
console.log(upperLetters); // ['H', 'E', 'L', 'L', 'O']

// Generate an array of objects
const users = Array.from({ length: 3 }, (_, i) => ({
    id: i + 1,
    name: `User ${i + 1}`,
    active: true
}));
console.log(users);
// [{id:1, name:'User 1', active:true}, {id:2, name:'User 2', active:true}, ...]
Note: The underscore _ in the mapping function is a common convention for a parameter you do not use. The first argument is the current element (which is undefined when creating from { length: N }), and the second is the index.

Accessing Elements by Index

You access individual elements in an array using bracket notation with the element's index. Remember that JavaScript arrays are zero-indexed: the first element is at index 0, the second at index 1, and so on. Trying to access an index that does not exist returns undefined rather than throwing an error.

Example: Accessing Elements by Index

const planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter'];

// Access individual elements
console.log(planets[0]); // 'Mercury' (first element)
console.log(planets[1]); // 'Venus'   (second element)
console.log(planets[2]); // 'Earth'   (third element)
console.log(planets[4]); // 'Jupiter' (fifth and last element)

// Access the last element using length
console.log(planets[planets.length - 1]); // 'Jupiter'

// Access an index that does not exist
console.log(planets[10]); // undefined (no error thrown)
console.log(planets[-1]); // undefined (negative indices do not work with brackets)

// Using a variable as the index
const index = 3;
console.log(planets[index]); // 'Mars'

Negative Indexing with the at() Method

ES2022 introduced the at() method, which provides a cleaner way to access elements using both positive and negative indices. With negative indices, at(-1) returns the last element, at(-2) returns the second-to-last, and so on. This eliminates the need for the array[array.length - 1] pattern.

Example: The at() Method

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

// Positive indices (same as bracket notation)
console.log(colors.at(0));  // 'red'
console.log(colors.at(2));  // 'yellow'

// Negative indices (counting from the end)
console.log(colors.at(-1)); // 'purple' (last element)
console.log(colors.at(-2)); // 'blue'   (second to last)
console.log(colors.at(-3)); // 'green'  (third to last)

// Compare with the old approach
console.log(colors[colors.length - 1]); // 'purple' (old way)
console.log(colors.at(-1));             // 'purple' (new way -- much cleaner)

// Useful in function returns
function getLastItem(arr) {
    return arr.at(-1);
}
console.log(getLastItem([10, 20, 30])); // 30
Tip: The at() method is supported in all modern browsers and Node.js 16.6+. Use it whenever you need to access elements from the end of an array. It makes your code more readable and less error-prone than the length - 1 pattern.

The length Property

Every array has a length property that returns the number of elements in the array. Unlike many other languages, the length property in JavaScript is writable -- you can set it to change the size of the array. Increasing the length adds empty slots; decreasing it removes elements from the end.

Example: Working with the length Property

const items = ['a', 'b', 'c', 'd', 'e'];
console.log(items.length); // 5

// The length is always one more than the highest index
console.log(items[items.length - 1]); // 'e'

// Setting length to a smaller value truncates the array
items.length = 3;
console.log(items); // ['a', 'b', 'c'] -- 'd' and 'e' are permanently removed

// Setting length to a larger value creates empty slots
items.length = 6;
console.log(items); // ['a', 'b', 'c', empty x 3]

// Emptying an array by setting length to 0
const numbers = [1, 2, 3, 4, 5];
numbers.length = 0;
console.log(numbers); // []

// Checking if an array is empty
const tasks = [];
if (tasks.length === 0) {
    console.log('No tasks to display.');
}
Caution: Truncating an array by setting length to a smaller value permanently removes those elements. There is no undo. Use this technique intentionally, not accidentally. If you need to keep the original data, create a copy first using slice() or the spread operator.

Modifying Elements

You can change any element in an array by assigning a new value to its index. You can also add new elements by assigning to an index beyond the current length of the array. Be careful with gaps, though -- assigning to an index far beyond the array's current length creates sparse arrays with empty slots in between.

Example: Modifying Array Elements

const languages = ['Python', 'JavaScript', 'Java', 'C++'];

// Modify an existing element
languages[1] = 'TypeScript';
console.log(languages); // ['Python', 'TypeScript', 'Java', 'C++']

// Add a new element at the next index
languages[4] = 'Rust';
console.log(languages); // ['Python', 'TypeScript', 'Java', 'C++', 'Rust']

// Creating a sparse array (avoid this!)
languages[10] = 'Go';
console.log(languages);
// ['Python', 'TypeScript', 'Java', 'C++', 'Rust', empty x 5, 'Go']
console.log(languages.length); // 11

// Check for empty slots
console.log(languages[7]); // undefined (empty slot)
console.log(7 in languages); // false (no element actually exists at index 7)

// Compare with a real undefined value
languages[8] = undefined;
console.log(languages[8]); // undefined (but the element exists)
console.log(8 in languages); // true
Note: Sparse arrays (arrays with gaps) can cause unexpected behavior with methods like forEach, map, and filter because they may skip empty slots. Avoid creating sparse arrays in your code.

Multidimensional Arrays

Since arrays can hold any value, they can also hold other arrays. An array of arrays is called a multidimensional array (or nested array). The most common type is a two-dimensional array, which is like a grid or table with rows and columns. You access elements using multiple bracket notations: the first bracket selects the row, and the second selects the column.

Example: Two-Dimensional Arrays

// A 3x3 grid (tic-tac-toe board)
const board = [
    ['X', 'O', 'X'],
    ['O', 'X', 'O'],
    ['X', ' ', 'O']
];

// Access individual cells
console.log(board[0][0]); // 'X' (first row, first column)
console.log(board[1][1]); // 'X' (second row, second column -- center)
console.log(board[2][1]); // ' ' (third row, second column)

// Modify a cell
board[2][1] = 'X';
console.log(board[2]); // ['X', 'X', 'O']

// A student grades table
const grades = [
    ['Ali',   95, 88, 92],
    ['Sara',  78, 85, 90],
    ['Omar',  100, 97, 95]
];

// Access Sara's second grade
console.log(grades[1][2]); // 85

// Loop through all grades
for (let row = 0; row < grades.length; row++) {
    const name = grades[row][0];
    const avg = (grades[row][1] + grades[row][2] + grades[row][3]) / 3;
    console.log(`${name}: average = ${avg.toFixed(1)}`);
}
// Ali: average = 91.7
// Sara: average = 84.3
// Omar: average = 97.3

Example: Three-Dimensional Array

// A 3D array representing a building with floors, rooms, and items
const building = [
    // Floor 0
    [
        ['desk', 'chair'],      // Room 0
        ['sofa', 'table', 'lamp'] // Room 1
    ],
    // Floor 1
    [
        ['bed', 'wardrobe'],    // Room 0
        ['bookshelf', 'plant']  // Room 1
    ]
];

// Access: Floor 1, Room 0, Item 1
console.log(building[1][0][1]); // 'wardrobe'

// Access: Floor 0, Room 1, Item 2
console.log(building[0][1][2]); // 'lamp'

Checking if a Value Is an Array: Array.isArray()

JavaScript's typeof operator returns 'object' for arrays, which is not helpful when you need to distinguish between a plain object and an array. The Array.isArray() static method reliably determines whether a value is an array. This is essential for writing robust functions that need to handle different input types.

Example: Array.isArray() in Action

// The problem with typeof
console.log(typeof [1, 2, 3]);     // 'object' (not helpful!)
console.log(typeof { a: 1 });      // 'object' (same result!)

// Array.isArray() solves this
console.log(Array.isArray([1, 2, 3]));    // true
console.log(Array.isArray([]));            // true
console.log(Array.isArray('hello'));       // false
console.log(Array.isArray({ a: 1 }));     // false
console.log(Array.isArray(123));           // false
console.log(Array.isArray(null));          // false
console.log(Array.isArray(undefined));     // false

// Practical use: a function that accepts a single value or an array
function processInput(input) {
    const items = Array.isArray(input) ? input : [input];
    // Now 'items' is always an array, regardless of input type
    items.forEach(item => console.log(`Processing: ${item}`));
}

processInput('single value');
// Processing: single value

processInput(['first', 'second', 'third']);
// Processing: first
// Processing: second
// Processing: third
Note: The instanceof operator (value instanceof Array) also works in most cases, but it can fail when dealing with arrays from different frames or windows in a browser. Array.isArray() is the universally reliable choice.

Array Destructuring

Destructuring is a modern JavaScript syntax (ES6) that allows you to unpack values from arrays into distinct variables. Instead of accessing elements one at a time with bracket notation, you can extract multiple values in a single, elegant statement. Destructuring makes your code shorter and more expressive.

Example: Basic Array Destructuring

const coordinates = [40.7128, -74.0060, 'New York'];

// Without destructuring
const lat = coordinates[0];
const lng = coordinates[1];
const city = coordinates[2];

// With destructuring (much cleaner)
const [latitude, longitude, cityName] = coordinates;
console.log(latitude);  // 40.7128
console.log(longitude); // -74.0060
console.log(cityName);  // 'New York'

// Skipping elements with commas
const colors = ['red', 'green', 'blue', 'yellow', 'purple'];
const [first, , third] = colors;
console.log(first); // 'red'
console.log(third); // 'blue' (green was skipped)

// Default values
const [a, b, c, d = 'default'] = [1, 2, 3];
console.log(d); // 'default' (no fourth element, so the default is used)

// Rest pattern: collect remaining elements
const [head, ...tail] = [10, 20, 30, 40, 50];
console.log(head); // 10
console.log(tail); // [20, 30, 40, 50]

// Swapping variables without a temp variable
let x = 'first';
let y = 'second';
[x, y] = [y, x];
console.log(x); // 'second'
console.log(y); // 'first'

Example: Destructuring with Functions

// A function that returns multiple values as an array
function getMinMax(numbers) {
    const min = Math.min(...numbers);
    const max = Math.max(...numbers);
    return [min, max];
}

const [minimum, maximum] = getMinMax([5, 2, 9, 1, 7]);
console.log(minimum); // 1
console.log(maximum); // 9

// Destructuring in a for...of loop
const entries = [['name', 'Ali'], ['age', 25], ['city', 'Dubai']];
for (const [key, value] of entries) {
    console.log(`${key}: ${value}`);
}
// name: Ali
// age: 25
// city: Dubai

// Nested destructuring
const matrix = [[1, 2], [3, 4]];
const [[a1, a2], [b1, b2]] = matrix;
console.log(a1, a2, b1, b2); // 1 2 3 4

The Spread Operator with Arrays

The spread operator (...) allows you to expand an array into individual elements. It is one of the most versatile features in modern JavaScript and is used for copying arrays, combining arrays, passing array elements as function arguments, and much more. The spread operator always creates a shallow copy, meaning nested objects and arrays inside the original are still shared by reference.

Example: Spread Operator Patterns

// Copying an array (shallow copy)
const original = [1, 2, 3, 4, 5];
const copy = [...original];
console.log(copy);            // [1, 2, 3, 4, 5]
console.log(copy === original); // false (different reference)

// Combining (concatenating) arrays
const frontend = ['HTML', 'CSS', 'JavaScript'];
const backend = ['Node.js', 'Python', 'Java'];
const fullstack = [...frontend, ...backend];
console.log(fullstack);
// ['HTML', 'CSS', 'JavaScript', 'Node.js', 'Python', 'Java']

// Adding elements at specific positions
const withMiddle = [...frontend, 'React', ...backend];
console.log(withMiddle);
// ['HTML', 'CSS', 'JavaScript', 'React', 'Node.js', 'Python', 'Java']

// Spreading into function arguments
const numbers = [5, 2, 9, 1, 7];
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1

// Converting a string to an array of characters
const chars = [...'Hello'];
console.log(chars); // ['H', 'e', 'l', 'l', 'o']

// Removing duplicates by combining with Set
const withDupes = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(withDupes)];
console.log(unique); // [1, 2, 3, 4]
Important: The spread operator creates a shallow copy. If your array contains objects or nested arrays, those inner references are shared, not duplicated. Modifying a nested object in the copy will also modify it in the original. For deep copies, use structuredClone() or a library like Lodash.

Example: Shallow Copy Pitfall

const users = [
    { name: 'Ali', age: 25 },
    { name: 'Sara', age: 30 }
];

const usersCopy = [...users];

// Modifying a nested object affects both arrays!
usersCopy[0].age = 99;
console.log(users[0].age);     // 99 (also changed in the original!)
console.log(usersCopy[0].age); // 99

// For a true deep copy, use structuredClone()
const usersDeep = structuredClone(users);
usersDeep[0].age = 50;
console.log(users[0].age);     // 99 (unchanged this time)
console.log(usersDeep[0].age); // 50

Real-World Examples

Let us put everything together with practical scenarios that you would encounter in real applications.

Example: Managing a Playlist

// Create a music playlist
const playlist = [
    { title: 'Bohemian Rhapsody', artist: 'Queen', duration: 355 },
    { title: 'Stairway to Heaven', artist: 'Led Zeppelin', duration: 482 },
    { title: 'Hotel California', artist: 'Eagles', duration: 391 },
    { title: 'Imagine', artist: 'John Lennon', duration: 183 }
];

// Access the first and last songs
const firstSong = playlist[0];
const lastSong = playlist.at(-1);
console.log(`Now playing: ${firstSong.title} by ${firstSong.artist}`);
console.log(`Up last: ${lastSong.title} by ${lastSong.artist}`);

// Calculate total playlist duration
let totalSeconds = 0;
for (let i = 0; i < playlist.length; i++) {
    totalSeconds += playlist[i].duration;
}
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
console.log(`Total playlist duration: ${minutes}m ${seconds}s`);

// Destructure song properties
const [{ title: firstTitle }, , { title: thirdTitle }] = playlist;
console.log(firstTitle); // 'Bohemian Rhapsody'
console.log(thirdTitle); // 'Hotel California'

Example: Processing Survey Results

// Survey responses as a 2D array [respondentId, rating, comment]
const responses = [
    [1, 5, 'Excellent service'],
    [2, 4, 'Very good'],
    [3, 3, 'Average experience'],
    [4, 5, 'Outstanding!'],
    [5, 2, 'Needs improvement'],
    [6, 4, 'Pretty good overall']
];

// Calculate average rating
let totalRating = 0;
for (let i = 0; i < responses.length; i++) {
    totalRating += responses[i][1];
}
const averageRating = totalRating / responses.length;
console.log(`Average rating: ${averageRating.toFixed(1)} / 5`);
// Average rating: 3.8 / 5

// Extract all ratings using destructuring in a loop
const ratings = [];
for (const [id, rating, comment] of responses) {
    ratings.push(rating);
    if (rating <= 2) {
        console.log(`Alert: Low rating (${rating}) from respondent ${id}: "${comment}"`);
    }
}

// Check if we have enough responses
if (Array.isArray(responses) && responses.length >= 5) {
    console.log('Sufficient data for analysis.');
}

// Create a copy for analysis without modifying the original
const analysisCopy = [...responses];
console.log(analysisCopy.length); // 6

Example: Building a Color Palette Generator

// Generate a palette of colors
function generatePalette(baseColors, variations) {
    if (!Array.isArray(baseColors)) {
        baseColors = [baseColors]; // Normalize to array
    }

    const palette = [];
    for (const color of baseColors) {
        for (let i = 0; i < variations; i++) {
            const lightness = 20 + (i * (60 / variations));
            palette.push(`hsl(${color}, 70%, ${lightness.toFixed(0)}%)`);
        }
    }
    return palette;
}

// Generate palettes for different hues
const warmPalette = generatePalette([0, 30, 60], 4);
console.log(warmPalette);
// ['hsl(0, 70%, 20%)', 'hsl(0, 70%, 35%)', ...]

// Combine palettes using spread
const coolHues = [180, 210, 240];
const coolPalette = generatePalette(coolHues, 3);
const fullPalette = [...warmPalette, ...coolPalette];
console.log(`Total colors in palette: ${fullPalette.length}`);

// Get the first 3 and remaining colors
const [primary, secondary, accent, ...rest] = fullPalette;
console.log(`Primary: ${primary}`);
console.log(`Secondary: ${secondary}`);
console.log(`Accent: ${accent}`);
console.log(`Remaining colors: ${rest.length}`);

Practical Exercise

Create a student grade book application. Start by creating an array of at least 5 student objects, each containing a name, an array of test scores, and a grade level. Then write code to: (1) Access the third student's second test score using bracket notation. (2) Use at(-1) to get the last student. (3) Use Array.isArray() to verify the scores property is an array. (4) Use destructuring to extract the first student's name and scores into separate variables. (5) Use the spread operator to create a combined array of all students' scores. (6) Create a 2D array that represents a seating chart with 3 rows and 4 seats per row, then access a specific seat. (7) Use Array.from() to generate an array of 10 random test scores between 60 and 100. Test each step and verify the output in your browser's console.

ES
Edrees Salih
21 hours ago

We are still cooking the magic in the way!