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
maptransforms every item in an array into something new - How
filterkeeps 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
functionkeyword - The name becomes a
constvariable =>replaces{andreturnfor single-expression functions — this is called implicit return- If the body needs multiple lines, you still use
{}and an explicitreturn:
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:
filterto Math studentsfilterto passing studentsmapto 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 ✍️.
- What does
mapreturn? What doesfilterreturn? - If you start with an array of 5 items and call
map, how many items are in the result? - If you start with an array of 5 items and call
filter, how many items could be in the result? - What is a predicate function?
- What's the difference between
mapandforEach? When would you useforEachinstead ofmap?
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
maptransforms every item and always returns a new array of the same lengthfiltertests every item and returns a new array with only the items that passforEachruns a function for each item but returns nothing — use it for side effects- Chain
filterandmapto build readable data pipelines - Neither
mapnorfiltermodifies 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.