Skip to editor content
learningjavascript.orglesson 5 of 25

Data Structures

Data structures are ways to organize and store multiple pieces of data together. Instead of having separate variables for each piece of information, you can group related data. In this lesson, we'll learn about JavaScript's two most important data structures: arrays and objects.

What You'll Learn

By the end of this lesson you will finish three working functions under check, all of them operating on one roster — an array of objects. One pulls a single field out of every record into a new array, one aggregates the whole roster down to a single average, and one folds it into a tally of how many records fall into each group.

You arrive able to loop a fixed number of times and make decisions inside that loop (04-control-flow); you leave able to point that machinery at a collection — to hold many related values under one name and walk through them.

This lesson feeds the capstone's task-manager-service milestone. It is not marked required for the final project, but the service you build in 15-capstone-project is precisely this data shape: the Task Manager keeps its tasks as an array of objects and answers every question — which are pending, how many are left — by walking that array and reading a field off each one. The milestone at the end of this lesson is where you confirm you can model and read it.

Arrays

Arrays store lists of values in order. Think of an array as a numbered list where each item has a position (index) starting from 0.

Creating Arrays

// Create an array of numbers
let numbers = [1, 2, 3, 4, 5];
console.log("Numbers:", numbers);

// Create an array of strings
let fruits = ["apple", "banana", "orange"];
console.log("Fruits:", fruits);

// Arrays can hold different types
let mixed = [1, "hello", true, 3.14];
console.log("Mixed:", mixed);

// Empty array
let empty = [];
console.log("Empty array:", empty);

Accessing Array Elements

let fruits = ["apple", "banana", "orange", "mango"];

// Access by index (starts at 0)
console.log("First fruit:", fruits[0]);
console.log("Second fruit:", fruits[1]);
console.log("Last fruit:", fruits[3]);

// Get array length
console.log("Number of fruits:", fruits.length);

// Access last element using length
console.log("Last fruit:", fruits[fruits.length - 1]);

Modifying Arrays

let numbers = [1, 2, 3];

// Change an element
numbers[1] = 20;
console.log("After change:", numbers);

// Add to the end
numbers.push(4);
console.log("After push:", numbers);

// Remove from the end
let removed = numbers.pop();
console.log("Removed:", removed);
console.log("After pop:", numbers);

// Add to the beginning
numbers.unshift(0);
console.log("After unshift:", numbers);

// Remove from the beginning
let first = numbers.shift();
console.log("First removed:", first);
console.log("After shift:", numbers);

Predict

Trace this by hand before running it. A value is pushed onto copy — but only copy, never original. What do all three lines log?

const original = [1, 2, 3];
const copy = original;
copy.push(4);

console.log("original:", original);
console.log("copy:", copy);
console.log("same array?", original === copy);

Array Methods

let numbers = [1, 2, 3, 4, 5];

// Find the index of an element
console.log("Index of 3:", numbers.indexOf(3));

// Check if array includes a value
console.log("Includes 4:", numbers.includes(4));
console.log("Includes 10:", numbers.includes(10));

// Join array into a string
let joined = numbers.join(", ");
console.log("Joined:", joined);

// Create a new array with a slice
let sliced = numbers.slice(1, 4);
console.log("Sliced (1 to 4):", sliced);
console.log("Original:", numbers);

Looping Through Arrays

let fruits = ["apple", "banana", "orange"];

// For loop
console.log("Using for loop:");
for (let i = 0; i < fruits.length; i++) {
  console.log(i + ":", fruits[i]);
}

// For...of loop (modern way)
console.log("\nUsing for...of:");
for (let fruit of fruits) {
  console.log(fruit);
}

// forEach method
console.log("\nUsing forEach:");
fruits.forEach(function(fruit, index) {
  console.log(index + ":", fruit);
});

Objects

Objects store data as key-value pairs. Instead of using numbers like arrays, objects use names (keys) to identify values.

Creating Objects

// Create an object
let person = {
  name: "Alice",
  age: 25,
  city: "New York"
};

console.log("Person:", person);

// Empty object
let empty = {};
console.log("Empty object:", empty);

Accessing Object Properties

let person = {
  name: "Alice",
  age: 25,
  city: "New York",
  isStudent: false
};

// Dot notation
console.log("Name:", person.name);
console.log("Age:", person.age);

// Bracket notation
console.log("City:", person["city"]);

// Bracket notation with variable
let property = "age";
console.log("Dynamic property:", person[property]);

Modifying Objects

let person = {
  name: "Bob",
  age: 30
};

console.log("Original:", person);

// Change a property
person.age = 31;
console.log("After age change:", person);

// Add a new property
person.city = "Boston";
console.log("After adding city:", person);

// Delete a property
delete person.age;
console.log("After deleting age:", person);

Objects with Methods

Objects can contain functions, called methods:

let calculator = {
  add: function(a, b) {
    return a + b;
  },
  subtract: function(a, b) {
    return a - b;
  },
  multiply: function(a, b) {
    return a * b;
  }
};

console.log("5 + 3 =", calculator.add(5, 3));
console.log("10 - 4 =", calculator.subtract(10, 4));
console.log("6 * 7 =", calculator.multiply(6, 7));

Looping Through Objects

let person = {
  name: "Charlie",
  age: 28,
  city: "Chicago",
  occupation: "Developer"
};

// For...in loop
console.log("Object properties:");
for (let key in person) {
  console.log(key + ":", person[key]);
}

// Get all keys
let keys = Object.keys(person);
console.log("\nAll keys:", keys);

// Get all values
let values = Object.values(person);
console.log("All values:", values);

Arrays of Objects

Combining arrays and objects is very common:

// Array of objects
let students = [
  { name: "Alice", grade: 85 },
  { name: "Bob", grade: 92 },
  { name: "Charlie", grade: 78 }
];

console.log("Students:");
for (let student of students) {
  console.log(student.name + " - Grade:", student.grade);
}

// Access specific student
console.log("\nFirst student:", students[0].name);

// Calculate average grade
let total = 0;
for (let student of students) {
  total += student.grade;
}
let average = total / students.length;
console.log("Average grade:", average);

Nested Structures

Objects can contain arrays and other objects:

let school = {
  name: "JavaScript High",
  students: ["Alice", "Bob", "Charlie"],
  location: {
    city: "Boston",
    state: "MA"
  }
};

console.log("School:", school.name);
console.log("Students:", school.students);
console.log("City:", school.location.city);

// Loop through nested array
console.log("\nStudent list:");
for (let student of school.students) {
  console.log("- " + student);
}

Debug

This code should print each student's city, using 'unknown' for anyone without an address. Instead it prints the first student's city and then crashes. Predict what it prints and why it stops, then fix it so every student prints.

const students = [
{ name: "Alice", address: { city: "Boston" } },
{ name: "Bob" },
{ name: "Charlie", address: { city: "Chicago" } },
];

console.log("Cities students live in:");
for (const student of students) {
console.log(student.name + ": " + student.address.city);
}

Expected output:

Cities students live in:
Alice: Boston
Bob: unknown
Charlie: Chicago

Recall

Without scrolling up: you have an array of student objects, each with a grade, and you want to count how many scored 80 or higher. Which two tools from earlier lessons do you combine to do it?

Ordering an Accumulator

Counting things by group is the single most common shape in this lesson's material, and it has an order that is not negotiable. Reassemble it.

Arrange the code

Reassemble the body of countByCity(list), which walks a list of student objects and returns an object mapping each city to how many students live there — { Boston: 2, Denver: 1 } for the roster shown. Five lines, two of them braces, and they must nest correctly. Find the only arrangement that runs and returns the right tally, then answer the real question: why must they run in this order and no other?

  1. const counts = {};
  2. }
  3. counts[student.city] = (counts[student.city] || 0) + 1;
  4. return counts;
  5. for (const student of list) {

Why that order? Create the container, fill it, return it. Most wrong arrangements stop the program outright — either a SyntaxError from a misplaced brace or a ReferenceError from using counts, list or student before it exists. But a handful do not: running all 120 arrangements of these five lines shows 15 exiting cleanly, of which only one is correct — so 14 run and give a wrong answer, and they fail in three different ways. Two return {"Boston":1}, because the return ended up inside the loop and the function hands back its tally after the very first student. Nine return an empty {}, because the counting line ended up below the return, where it is dead code, leaving the loop body empty. Three print nothing at all: a closing brace landed straight after the function header, so the function body is empty and return counts; is left at the top level of the file — and because Node wraps each file in a function before running it, a top-level return is legal and ends the whole module before the console.log is ever reached. Those are the dangerous ones — no error, no clue, just a tally that is quietly too small or an Output pane that is quietly empty.

Try It Yourself

Reading about arrays and objects is not the same as building with them. This is a build task: a small program that reports its own pass/fail. You are given a roster of students — an array of objects — and three empty functions to finish. Run it and it fails immediately, telling you which function is still missing. Implement each one until every check passes and it prints All checks passed.

The three functions reuse exactly what this lesson taught: looping over an array of objects, reading a field with dot notation, aggregating with a running total, and building up an object as you go. The starter already has the data, the stubs, and the checks — you write only the logic inside each function. Nothing above spells out all three answers, so you will have to assemble them yourself.

Build

Finish the build. Three functions are stubbed out and the checks below them fail until each one returns the right value. Run it as-is to see which check fails first, decide what that function is missing, then implement the three functions until it prints 'All checks passed.' The checks run top to bottom, so the first failure you see is TODO 1 — implement it first, then work down.

const assert = require('assert');

// The data: a class roster. Do NOT change this array.
const students = [
{ name: "Alice", city: "Boston", grade: 91 },
{ name: "Bob", city: "Boston", grade: 78 },
{ name: "Carol", city: "Denver", grade: 84 },
{ name: "Dan", city: "Denver", grade: 88 },
{ name: "Eve", city: "Boston", grade: 72 },
];

// TODO 1: return a NEW array of just the names, in order.
//   names(students) -> ["Alice", "Bob", "Carol", "Dan", "Eve"]
function names(students) {
// your code here
}

// TODO 2: return the class average grade as a number.
//   averageGrade(students) -> 82.6
function averageGrade(students) {
// your code here
}

// TODO 3: return an object counting how many students are in each city.
//   countByCity(students) -> { Boston: 3, Denver: 2 }
function countByCity(students) {
// your code here
}

// --- Build checks: these must all pass. Do not edit below this line. ---
assert.deepStrictEqual(
names(students),
["Alice", "Bob", "Carol", "Dan", "Eve"],
"names(students) should return an array of every student's name, in order",
);
assert.strictEqual(
averageGrade(students),
82.6,
"averageGrade(students) should return the mean of all grades (82.6)",
);
assert.deepStrictEqual(
countByCity(students),
{ Boston: 3, Denver: 2 },
"countByCity(students) should map each city to how many students live there",
);

console.log("All checks passed.");
console.log("Names:", names(students));
console.log("Average grade:", averageGrade(students));
console.log("By city:", countByCity(students));

Expected output:

All checks passed.
Names: [ 'Alice', 'Bob', 'Carol', 'Dan', 'Eve' ]
Average grade: 82.6
By city: { Boston: 3, Denver: 2 }

Once it passes, try two variations and predict each before running:

  1. Push the whole student. In names, push student instead of student.name. Predict the shape of what names(students) returns before running. You get an array of the full objects, not their names, so the deepStrictEqual against ["Alice", ...] fails — a reminder that building a projection means pulling out the one field, not the whole record.
  2. Drop the first-seen guard. In countByCity, count with a bare counts[student.city] = counts[student.city] + 1, with no branch that starts an unseen city at 0. Predict what countByCity(students) returns before running. The first time a city is seen, counts[city] is undefined, and undefined + 1 is NaN — so every city comes out NaN, showing why the count-up object needs an initial value.

Capstone milestone

Milestone — the data behind the Task Manager service. The capstone keeps its tasks as an array of task objects, each with fields like title and status, and the service reads and updates that array. This lesson's arrays-of-objects are that exact data shape in miniature. Confirm you can model and read it.

Hint: You don't need the capstone service itself yet — this confirms the array-of-objects data model it sits on. In the capstone, the service holds tasks as exactly this kind of array and reads their fields the same way.

  • Built an array of objects where each object holds several named fields
  • Accessed a field on one object with dot or bracket notation
  • Looped over the array to read a field from every object
  • Aggregated across the array (a count, a total, or an average) with a loop

Taking It Somewhere New

Transfer

You have been building objects whose keys and value kinds you simply remember — a student has name, city and grade, and you know that because you wrote it. New setting: TypeScript, where you can write that shape down as type Student = { name: string; city: string; grade: number } and put it on the array. Apply what you know: which mistakes in this lesson would that have caught, and which would it not?

const students = [
{ name: "Alice", city: "Boston", grade: 91 },
{ name: "Bob", city: "Boston", grade: 78 },
];

// A typo in the key name. Runs fine, prints undefined.
console.log(students[0].cty);

// An optional nested key. Reading it is a harmless undefined;
// reaching THROUGH it throws a TypeError.
const first = { name: "Carol" };
console.log(first.address);
console.log(first.address.city);

Key Takeaways

  • Arrays store ordered lists of values, accessed by index (0, 1, 2, ...)
  • Use arrays when you need an ordered collection of similar items
  • Objects store key-value pairs, accessed by property name
  • Use objects when you need to group related properties together
  • Arrays and objects can be nested to create complex data structures
  • Common array methods: push(), pop(), shift(), unshift(), indexOf(), includes()
  • Access object properties with dot notation (obj.key) or bracket notation (obj["key"])

Next Steps

You can now organize data with arrays and objects — which sets up the next core lesson perfectly. Next you'll learn array methods like map, filter, and reduce: the tools that turn the arrays you just met into concise, expressive data pipelines you'll reach for constantly.

Pro Tip: The best way to master data structures is to use them in real projects. Try creating a todo list with an array of objects, or a contact book using nested objects. Practice makes perfect!

Next lesson

Array Methods

JavaScript array methods tutorial — master map, filter, reduce, find, some, every, and more with interactive examples you can edit and run

25 min