Callbacks and Array Methods

Introduction

The Problem with Loops

In CS Fundamentals 3, you wrote loops like this:

const scores = [85, 92, 78, 96];

for (let i = 0; i < scores.length; i++) {
  console.log(scores[i]);
}

That works. But it has problems:

  • You manage the counter (i) manually
  • It's easy to write <= instead of < and get a bug
  • Every loop looks nearly the same — but you still have to read it carefully to know what it does

There's a better way. JavaScript has built-in methods that loop for you — and let you describe what you want to happen instead of how to make it happen step by step. This style is called declarative programming. You'll hear that word again when you get to React.

What You'll Learn Today

  • What a callback function is and how it works
  • How map transforms every item in an array into something new
  • How filter keeps only the items that pass a test
  • When to use each one

Before You Begin

Accept the GitHub Classroom assignment and clone the repository. Follow the README.md to get set up. You'll code along directly in this repo — commit your work as you go.


Arrow Functions: A Shorter Way to Write Functions

You already know how to write functions:

function double(num) {
  return num * 2;
}

JavaScript has a second, shorter syntax for the same thing — the arrow function:

const double = (num) => num * 2;

Same behavior. Less typing. You'll see arrow functions everywhere in modern JavaScript, especially as callbacks.

Here's the pattern side by side:

// Function declaration
function add(a, b) {
  return a + b;
}

// Arrow function — same thing
const add = (a, b) => a + b;

The arrow function version is a function expression — a function stored in a variable. The key difference from a declaration is that the function itself has no name; the variable (add) is what you use to call it.

A few things to notice:

  • No function keyword
  • The name becomes a const variable
  • => replaces { and return for single-expression functions — this is called implicit return
  • If the body needs multiple lines, you still use {} and an explicit return:
const getLetterGrade = (score) => {
  if (score >= 90) return "A";
  if (score >= 80) return "B";
  if (score >= 70) return "C";
  return "F";
};

For the rest of this lesson, you'll see arrow functions used as callbacks. That's where they really shine — short, inline, readable.


What Is a Callback Function?

Imagine you're at a restaurant. You tell the waiter: "When my food is ready, call me back — text me." You handed him an instruction to follow later. That's a callback.

A callback function is a function you pass to another function as an argument.

You've already passed values to functions:

console.log("Hello"); // passing a string
Math.max(3, 7);       // passing numbers

A callback is the same idea — except you're passing a function instead of a value. This works because in JavaScript, functions are first-class — they can be passed around just like strings or numbers.

// A regular function
const double = (num) => num * 2;

// Pass it as a callback to map
const scores = [1, 2, 3];
const doubled = scores.map(double);

console.log(doubled); // [2, 4, 6]

map calls double once for each item in the array. That's what "callback" means — map calls your function back for each element.

Most of the time, you'll write the callback inline instead of naming it separately:

const doubled = scores.map((num) => num * 2);

Same result. One less variable to name.


map: Transform Every Item

map creates a new array by running your callback on each item. The original array is never changed.

const scores = [85, 92, 78, 96];

const curved = scores.map((score) => score + 5);

console.log(scores);  // [85, 92, 78, 96] — original unchanged
console.log(curved);  // [90, 97, 83, 101] — new array

The new array always has the same number of items as the original. Every item goes in, every item comes out — just transformed.

Practice 1: Simple Transformation

Start with this array:

const names = ["alice", "bob", "carol"];

Use map to create a new array where every name starts with a capital letter.

Hint

Strings have a method called .toUpperCase(). But that capitalizes the whole string. You want just the first letter.

Try: `${name[0].toUpperCase()}${name.slice(1)}`

Solution
const names = ["alice", "bob", "carol"];

const capitalized = names.map(
  (name) => `${name[0].toUpperCase()}${name.slice(1)}`
);

console.log(capitalized); // ["Alice", "Bob", "Carol"]

Practice 2: Transforming Objects

map works on arrays of objects too. This is where it gets useful.

const students = [
  { name: "Alice", grade: 88 },
  { name: "Bob", grade: 72 },
  { name: "Carol", grade: 95 },
];

Use map to create a new array of strings like "Alice: 88" for each student.

Hint

Your callback receives one student object at a time. Use dot notation to access its properties inside a template literal.

Solution
const summaries = students.map(
  (student) => `${student.name}: ${student.grade}`
);

console.log(summaries);
// ["Alice: 88", "Bob: 72", "Carol: 95"]

Practice 3: Adding a Property

You can use map to build new objects based on existing ones. The spread operator (...) copies all existing properties so you don't have to retype them.

const students = [
  { name: "Alice", grade: 88 },
  { name: "Bob", grade: 72 },
  { name: "Carol", grade: 95 },
];

Use map to create a new array of student objects, each with an added passing property. A student is passing if their grade is 70 or above.

Hint
// Spread copies the existing properties, then you add new ones:
const updated = { ...student, passing: true };

Use a ternary for the passing value — you've used those before.

Solution
const withPassingStatus = students.map((student) => ({
  ...student,
  passing: student.grade >= 70,
}));

console.log(withPassingStatus);
// [
//   { name: "Alice", grade: 88, passing: true },
//   { name: "Bob",   grade: 72, passing: true },
//   { name: "Carol", grade: 95, passing: true },
// ]

Notice the parentheses around the {}. When you return an object with arrow function shorthand, you need them — otherwise JavaScript thinks the {} is the function body.


filter: Keep Only What Passes

filter creates a new array containing only the items where your callback returns true. Items where the callback returns false are left out.

const scores = [85, 92, 78, 96, 61];

const passing = scores.filter((score) => score >= 70);

console.log(scores);   // [85, 92, 78, 96, 61] — original unchanged
console.log(passing);  // [85, 92, 78, 96]

The new array may have fewer items than the original — or even zero, if nothing passes.

A function that returns true or false is called a predicate. Every callback you pass to filter is a predicate.

Practice 4: Filter by a Number Condition

const students = [
  { name: "Alice", grade: 88 },
  { name: "Bob", grade: 72 },
  { name: "Carol", grade: 95 },
  { name: "Dave", grade: 61 },
];

Use filter to create a new array containing only the students on the honor roll (grade 90 or above).

Hint

Your callback receives one student object. Return true to keep the student, false to leave them out.

Solution
const honorRoll = students.filter((student) => student.grade >= 90);

console.log(honorRoll);
// [{ name: "Carol", grade: 95 }]

Practice 5: Filter by a String Condition

const students = [
  { name: "Alice", grade: 88, subject: "Math" },
  { name: "Bob", grade: 72, subject: "History" },
  { name: "Carol", grade: 95, subject: "Math" },
  { name: "Dave", grade: 61, subject: "History" },
];

Use filter to get only the Math students.

Solution
const mathStudents = students.filter(
  (student) => student.subject === "Math"
);

console.log(mathStudents);
// [
//   { name: "Alice", grade: 88, subject: "Math" },
//   { name: "Carol", grade: 95, subject: "Math" },
// ]

Practice 6: Chaining filter and map

You can chain array methods. The output of one becomes the input of the next.

const students = [
  { name: "Alice", grade: 88, subject: "Math" },
  { name: "Bob", grade: 72, subject: "History" },
  { name: "Carol", grade: 95, subject: "Math" },
  { name: "Dave", grade: 61, subject: "History" },
];

Get the names of all Math students who are passing (grade >= 70). The final result should be an array of strings.

Hint

Break it into steps:

  1. filter to Math students
  2. filter to passing students
  3. map to just the names

You can chain all three on separate lines.

Solution
const passingMathStudentNames = students
  .filter((student) => student.subject === "Math")
  .filter((student) => student.grade >= 70)
  .map((student) => student.name);

console.log(passingMathStudentNames); // ["Alice", "Carol"]

Each method runs on the result of the previous one. Read it top to bottom — filter to Math, filter to passing, pull out the names.


forEach: When You Just Want to Do Something

map and filter always return new arrays. Sometimes you don't need a new array — you just want to run some code for each item.

That's forEach. It loops, runs your callback, and returns nothing.

const students = ["Alice", "Bob", "Carol"];

students.forEach((name) => {
  console.log(`Hello, ${name}!`);
});

// Hello, Alice!
// Hello, Bob!
// Hello, Carol!

forEach is for side effects — logging, updating the DOM, saving to a database. Anything where the point is the action itself, not a new value.

In practice, you'll use map and filter far more often than forEach.


Checkpoint

Write your answers in handwritten notes ✍️.

  1. What does map return? What does filter return?
  2. If you start with an array of 5 items and call map, how many items are in the result?
  3. If you start with an array of 5 items and call filter, how many items could be in the result?
  4. What is a predicate function?
  5. What's the difference between map and forEach? When would you use forEach instead of map?

Common Mistakes

Forgetting to Return in map

const scores = [85, 92, 78];

// ❌ Missing return — every item becomes undefined
const bad = scores.map((score) => {
  score + 5;
});
console.log(bad); // [undefined, undefined, undefined]

// ✅ Explicit return
const good = scores.map((score) => {
  return score + 5;
});

// ✅ Implicit return (arrow function shorthand — no curly braces needed)
const better = scores.map((score) => score + 5);

Mutating Instead of Creating

const students = [
  { name: "Alice", grade: 85 },
];

// ❌ Mutates the original object
const bad = students.map((student) => {
  student.grade += 5; // changing the original!
  return student;
});

// ✅ Creates a new object with spread
const good = students.map((student) => ({
  ...student,
  grade: student.grade + 5,
}));

console.log(students[0].grade); // 85 — original unchanged ✅

Wrap-Up

Key Takeaways

  • A callback is a function you pass to another function to be called for each item
  • map transforms every item and always returns a new array of the same length
  • filter tests every item and returns a new array with only the items that pass
  • forEach runs a function for each item but returns nothing — use it for side effects
  • Chain filter and map to build readable data pipelines
  • Neither map nor filter modifies the original array

What's Next

You know how to process arrays of objects. Next, you'll learn tools for working on the objects themselves — Object.keys(), Object.values(), Object.entries(), and spread — and see how they pair directly with the map and filter you just learned.