Callbacks and Array Methods

Introduction

From Manual Processing to Automated Workflows

Icebreaker: Imagine you're a teacher with a stack of 200 exam papers. You need to:

  • Grade each paper (add points)
  • Filter out the papers that scored above 90%
  • Write encouraging comments on each one

Would you rather process each paper individually by hand, or have a smart assistant that can follow your instructions and process the entire stack automatically?

Real-world scenario: Every day, apps process thousands of data items - user profiles, shopping cart items, social media posts. Instead of writing repetitive code for each item, developers use callback functions with array methods to process entire collections efficiently.

What You'll Learn Today

  • How callback functions work (your instructions to the "smart assistant")
  • Essential array methods: forEach, map, and filter
  • When to use each method for different data processing tasks
  • How to transform and filter real-world data collections

Introduction to Callbacks

A callback function is like giving someone a set of instructions (i.e. a reusable code block - a function!) to follow for each item in a collection. You define what should happen, and the array method applies those instructions to every element. It applies these instructions by calling your function back for each item.

Core Concept Overview

What is a Callback Function?

Mental model: Think of a callback as instructions you give to a helper. You say "For each student in this class, do THIS" - and the callback is the "THIS" part.

// Your instruction manual (callback function)
const gradeExam = (studentExam) => {
  // * Note: Functions should have explicit return values
  return `Grading ${studentExam.name}'s exam: ${studentExam.score}/100`;
};

const exams = [
  { name: "Alex", score: 95 },
  { name: "Jordan", score: 87 },
  { name: "Taylor", score: 92 },
];

// "For each exam, follow the gradeExam instructions"
const gradeResults = exams.map(gradeExam);
console.log(gradeResults);

// Or if you just want to display (forEach for side effects only):
exams.forEach((exam) => {
  console.log(`Grading ${exam.name}'s exam: ${exam.score}/100`);
});

The Three Essential Array Methods

Mental model: Think of these as three different types of assembly line workers:

  1. map: - takes each item, calls the callback function to create new data, and creates a new array with all of the new data. The new data is nothing but the original items, but has been created from the callback function's return. This means that the newly created array always has the same number of items as the original array.
  2. filter: The quality inspector - examines each item and keeps only the ones that pass the test. The kept items are put into a newly created array. The original array is not affected/mutated. The newly created array will have the same or fewer items as the original array. It may even be zero items if none of the original items pass the callback function true/false condition.
  3. forEach: The announcer - looks at each item and does something with it, but doesn't create a new collection. We won't use this as much as the other two.

map: The Transformer

Analogy: Like a factory assembly line that takes raw materials and transforms them into finished products - you get a completely new collection.

const grades = [85, 92, 78, 96, 88];

// Transform each grade into a letter grade
const letterGrades = grades.map((numericGrade) => {
  if (numericGrade >= 90) return "A";
  if (numericGrade >= 80) return "B";
  if (numericGrade >= 70) return "C";
  return "F";
});

console.log(grades); // [85, 92, 78, 96, 88] - original unchanged
console.log(letterGrades); // ["B", "A", "C", "A", "B"] - new array created

Key concept: map always returns a new array of the same length, with each element transformed.

filter: The Quality Inspector

Analogy: Like a bouncer at a club checking IDs - only items that meet the criteria get through to the new collection.

const ages = [16, 21, 19, 25, 17, 22];

// Only keep ages 21 and over
const adultAges = ages.filter((age) => {
  return age >= 21;
});

console.log(ages); // [16, 21, 19, 25, 17, 22] - original unchanged
console.log(adultAges); // [21, 25, 22] - only items that passed the test

Key concept: filter returns a new array containing only items that pass the test (return true).

Bonus Array Methods: every & some

Mental model: Think of these as group decision makers:

  • every: The "All-Star Vote" – returns true if every item in the array passes the test you give (callback returns true).
  • some: The "At Least One Vote" – returns true if at least one item in the array passes the test.

every: Require All Items to Pass

Analogy: Like a school rule that ALL students must have turned in their homework for a class reward.

const homeworkStatus = [true, true, false, true];

// Did every student submit homework?
const allSubmitted = homeworkStatus.every((status) => status === true);

console.log(allSubmitted); // false (not everyone submitted)

some: Succeed If Any Item Passes

Analogy: Like a contest where ANY student who gets an A wins a prize.

const grades = [67, 84, 75, 90, 82];

// Did anyone get an A (90 or above)?
const hasAStudent = grades.some((grade) => grade >= 90);

console.log(hasAStudent); // true

Use cases:

  • Use every when you need ALL items to satisfy a rule (e.g., "Do all users have permission?")
  • Use some when ANY item passing is enough (e.g., "Is there any product out of stock?")

Key Terms

  • Callback function: A function passed to another function to be executed later
  • forEach: Array method that executes a callback for each element (no return value)
  • map: Array method that transforms each element and returns a new array
  • filter: Array method that tests each element and returns a new array with passing items
  • Return value: What a function gives back when it finishes
  • Predicate: A function that returns true or false (used with filter)

Conceptual Quiz

Test your understanding of these core concepts:

  1. What does a callback function represent?

    • a) A function that calls other functions
    • b) Instructions for what to do with each array element
    • c) A function that returns arrays
    • d) A method for creating objects
  2. Which method would you use to create a new array where each student's name is converted to uppercase?

    • a) forEach
    • b) map
    • c) filter
    • d) push
  3. Which method would you use to find all students with grades above 90?

    • a) forEach
    • b) map
    • c) filter
    • d) indexOf
  4. What happens to the original array when you use map?

    • a) It gets modified
    • b) It gets deleted
    • c) It stays unchanged
    • d) It gets copied
  5. If you have an array of 5 items and use map on it, how many items will the result have?

    • a) It depends on the callback function
    • b) Always 5
    • c) Could be less than 5
    • d) Could be more than 5

Answers: 1-b, 2-b, 3-c, 4-c, 5-b

Hands-On Application

Exercise 1: Student Grade Processing

Scenario: You're building a grade management system and need to process student data efficiently.

// Starting data - array of student objects
const students = [
  { name: "Alex Chen", grade: 95, subject: "Math" },
  { name: "Jordan Smith", grade: 87, subject: "Science" },
  { name: "Taylor Johnson", grade: 92, subject: "Math" },
  { name: "Casey Davis", grade: 78, subject: "Science" },
  { name: "Morgan Lee", grade: 96, subject: "Math" },
];

// 1. TRANSFORM: Create display names (map) - START HERE!
const displayNames = students.map((student) => {
  return `${student.name} (${student.grade}%)`;
});
console.log("Display names:", displayNames);

// 2. FILTER: Find honor roll students (90% or above)
const honorRoll = students.filter((student) => {
  return student.grade >= 90;
});
console.log("Honor roll students:", honorRoll);

Your turn: Add a property isHonorRoll: true/false to each student using map.

Exercise 2: E-commerce Product Processing

Scenario: You're working on an online store and need to process product data.

const products = [
  { name: "Laptop", price: 899, category: "Electronics", inStock: true },
  { name: "Coffee Mug", price: 15, category: "Kitchen", inStock: false },
  { name: "Notebook", price: 8, category: "Office", inStock: true },
  { name: "Headphones", price: 199, category: "Electronics", inStock: true },
  { name: "Desk Lamp", price: 45, category: "Office", inStock: false },
];

// Create sale prices (map) - 20% off everything
const saleProducts = products.map((product) => {
  const salePrice = (product.price * 0.8).toFixed(2);
  return {
    ...product,
    originalPrice: product.price,
    price: parseFloat(salePrice),
    onSale: true,
  };
});

console.log("Sale products:", saleProducts);

// Find available electronics (filter + filter chaining)
const availableElectronics = products
  .filter((product) => product.inStock)
  .filter((product) => product.category === "Electronics");

console.log("Available electronics:", availableElectronics);

// Display all products (forEach)
console.log("=== Product Catalog ===");
products.forEach((product) => {
  const availability = product.inStock ? "✅ In Stock" : "❌ Out of Stock";
  console.log(`${product.name} - $${product.price} ${availability}`);
});

Exercise 3: Social Media Posts Processing

Scenario: You're building a social media dashboard and need to process post data.

Exercise 4: Chaining Methods Together

Mental model: Think of method chaining like an assembly line where each station does one specific job.

Advanced Concepts & Comparisons

Arrow Functions vs Regular Functions in Callbacks

Best practice: Use arrow functions for simple callbacks - they're more concise and readable.

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

// Arrow function (preferred for simple callbacks)
const doubled = numbers.map((num) => num * 2);

// Regular function (longer but sometimes clearer for complex logic)
const doubledVerbose = numbers.map(function (num) {
  return num * 2;
});

// Both produce the same result: [2, 4, 6, 8, 10]

Understanding Return Values

Mental model: Think of return values like what each worker hands to the next station on an assembly line.

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

// forEach returns undefined (nothing passed to next station)
const result1 = scores.forEach((score) => console.log(score));
console.log(result1); // undefined

// map returns new array (passes transformed items to next station)
const result2 = scores.map((score) => score + 5);
console.log(result2); // [90, 97, 83, 101]

// filter returns new array (passes qualifying items to next station)
const result3 = scores.filter((score) => score >= 90);
console.log(result3); // [92, 96]

Common Patterns: Building Complex Transformations

const courseData = [
  { course: "Math 101", students: 25, avgGrade: 87 },
  { course: "Science 201", students: 18, avgGrade: 92 },
  { course: "History 101", students: 22, avgGrade: 78 },
  { course: "Art 150", students: 15, avgGrade: 95 },
];

// Complex transformation: Find successful courses and format for display
const successfulCourses = courseData
  .filter((course) => course.avgGrade >= 85) // Quality control
  .filter((course) => course.students >= 20) // Size requirement
  .map((course) => ({
    // Transform for display
    title: course.course,
    performance: course.avgGrade >= 90 ? "Excellent" : "Good",
    enrollment: `${course.students} students`,
    summary: `${course.course}: ${course.avgGrade}% avg (${course.students} students)`,
  }));

console.log("Successful courses:", successfulCourses);

Troubleshooting & Best Practices

Common Mistakes with Callbacks

The "Forgetting Return" Mistake:

const numbers = [1, 2, 3, 4];

// ❌ Forgot to return in map
const bad = numbers.map((num) => {
  num * 2; // Missing return!
});
console.log(bad); // [undefined, undefined, undefined, undefined]

// ✅ Remember to return the transformed value
const good = numbers.map((num) => {
  return num * 2;
});
console.log(good); // [2, 4, 6, 8]

// ✅ Or use implicit return with arrow functions
const better = numbers.map((num) => num * 2);
console.log(better); // [2, 4, 6, 8]

The "Modifying Original Array" Mistake:

const students = [
  { name: "Alex", grade: 85 },
  { name: "Jordan", grade: 92 },
];

// ❌ This modifies the original objects
const badBonusGrades = students.map((student) => {
  student.grade += 5; // Modifying the original!
  return student;
});

// ✅ Create new objects instead
const goodBonusGrades = students.map((student) => {
  return {
    ...student,
    grade: student.grade + 5,
  };
});

console.log(students); // Original should be unchanged

Choosing the Right Method

Decision tree:

  1. Just want to do something with each item? → Use forEach
  2. Want to transform each item into something else? → Use map
  3. Want to keep only items that meet certain criteria? → Use filter
const inventory = [
  { item: "Laptop", price: 800, quantity: 5 },
  { item: "Mouse", price: 25, quantity: 50 },
  { item: "Keyboard", price: 75, quantity: 0 },
];

// DISPLAY (forEach) - just show information
inventory.forEach((item) => {
  console.log(`${item.item}: $${item.price} (${item.quantity} in stock)`);
});

// TRANSFORM (map) - calculate total values
const itemValues = inventory.map((item) => ({
  item: item.item,
  totalValue: item.price * item.quantity,
}));

// SELECT (filter) - find items in stock
const inStock = inventory.filter((item) => item.quantity > 0);

Performance Considerations

Best practice: Chain methods efficiently and avoid unnecessary operations.

const largeDataSet = [
  /* imagine thousands of items */
];

// ✅ Efficient: Filter first (reduces items to process)
const result = largeDataSet
  .filter((item) => item.isActive) // Reduce dataset first
  .map((item) => expensiveTransform(item)); // Then transform fewer items

// ❌ Less efficient: Map first (processes all items)
const inefficient = largeDataSet
  .map((item) => expensiveTransform(item)) // Transforms everything
  .filter((item) => item.isActive); // Then throws most away

Wrap-Up & Assessment

Key Takeaways

  1. Callback functions are instructions you give to array methods
  2. forEach is for doing something with each item (no return value needed)
  3. map transforms each item and always returns a new array of the same length
  4. filter tests each item and returns a new array with only passing items
  5. Method chaining lets you build complex data processing pipelines
  6. Always return values in map and filter callbacks

Advanced Conceptual Quiz

Test your deeper understanding:

  1. What will this code output?

    const nums = [1, 2, 3];
    const result = nums.map((n) => n > 2);
    console.log(result);
    
    • a) [3]
    • b) [false, false, true]
    • c) [1, 2, 3]
    • d) true
  2. What's wrong with this code?

    const prices = [10, 20, 30];
    const doubled = prices.map((price) => {
      price * 2;
    });
    
    • a) Missing parentheses
    • b) Missing return statement
    • c) Wrong method choice
    • d) Nothing is wrong

Answers: 1-b, 2-b

HW: Data Processing Dashboard

Part 1: Student Performance Analytics

Create a comprehensive student analytics system:

Part 2: E-commerce Order Processing

Part 3: Social Media Content Moderation

Submission Requirements:

  • Working code for all three parts with console output screenshots. You will be assigned a GitHub Classroom repository to submit your code. Commit early, commit often!
  • Demo video (5-6 minutes): Walk through one complete example from each part
  • Reflection: Which array method felt most natural? Which was most challenging? Additionally, discuss the 'self check' quiz questions and answers. The answers were provided, so reflect on your thought process and understanding. Explain why those answers are correct.

Real-World Connection

The patterns you've learned today are used everywhere in modern web development:

  • React: Rendering lists of components with map
  • APIs: Processing server responses with filter and map
  • Forms: Validating user input with filter
  • Analytics: Processing user behavior data with all three methods

Next up: We'll review all JavaScript fundamentals and see how these concepts prepare you for React development!