Array Methods: Map, Filter, and Reduce

Mastering JavaScript's Powerful Array Transformation Methods

The Power of Functional Array Methods

Imagine you're a chef with a conveyor belt of ingredients. The map method transforms each ingredient (like chopping vegetables), filter selects only certain ingredients (like picking ripe tomatoes), and reduce combines everything into a final dish (like making a soup). These methods help you write cleaner, more declarative code.

graph LR A[Original Array] --> B{map} B --> C[Transformed Array] A --> D{filter} D --> E[Filtered Array] A --> F{reduce} F --> G[Single Value] style A fill:#f9f,stroke:#333,stroke-width:4px style C fill:#bfb,stroke:#333,stroke-width:2px style E fill:#bfb,stroke:#333,stroke-width:2px style G fill:#bfb,stroke:#333,stroke-width:2px

Understanding map()

The map() method creates a new array by calling a function on every element in the original array. It's like having a magic wand that transforms each item in a list.

Basic Syntax

// array.map(callback(currentValue[, index[, array]])[, thisArg])

const newArray = originalArray.map((item, index, array) => {
    // return transformed item
});

Simple Examples

// Double all numbers
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// Convert to uppercase
const fruits = ['apple', 'banana', 'orange'];
const uppercased = fruits.map(fruit => fruit.toUpperCase());
console.log(uppercased); // ['APPLE', 'BANANA', 'ORANGE']

// Extract property from objects
const users = [
    { id: 1, name: 'John', age: 30 },
    { id: 2, name: 'Jane', age: 25 },
    { id: 3, name: 'Bob', age: 35 }
];
const names = users.map(user => user.name);
console.log(names); // ['John', 'Jane', 'Bob']

Real-World Examples

// Format prices for display
const products = [
    { name: 'Laptop', price: 999.99 },
    { name: 'Phone', price: 599.99 },
    { name: 'Tablet', price: 349.99 }
];

const formattedProducts = products.map(product => ({
    ...product,
    displayPrice: `$${product.price.toFixed(2)}`,
    priceWithTax: product.price * 1.08
}));

console.log(formattedProducts);

// Convert API data for UI
const apiResponse = [
    { user_id: 1, first_name: 'John', last_name: 'Doe', email_address: 'john@example.com' },
    { user_id: 2, first_name: 'Jane', last_name: 'Smith', email_address: 'jane@example.com' }
];

const uiData = apiResponse.map(user => ({
    id: user.user_id,
    fullName: `${user.first_name} ${user.last_name}`,
    email: user.email_address,
    initials: `${user.first_name[0]}${user.last_name[0]}`
}));

// Create HTML elements
const items = ['Home', 'About', 'Services', 'Contact'];
const navItems = items.map(item => `
  • ${item}
  • `); const navigation = `
      ${navItems.join('')}
    `; // Process form data const formData = [ { field: 'username', value: ' JohnDoe123 ' }, { field: 'email', value: 'JOHN@EXAMPLE.COM' }, { field: 'age', value: '25' } ]; const processedData = formData.map(item => ({ field: item.field, value: item.field === 'email' ? item.value.toLowerCase() : item.field === 'username' ? item.value.trim() : item.field === 'age' ? parseInt(item.value) : item.value }));

    Understanding filter()

    The filter() method creates a new array with all elements that pass a test. It's like a bouncer at a club, only letting in elements that meet certain criteria.

    Basic Syntax

    // array.filter(callback(element[, index[, array]])[, thisArg])
    
    const filteredArray = originalArray.filter((item, index, array) => {
        // return true to keep the item, false to filter it out
    });

    Simple Examples

    // Filter even numbers
    const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    const evenNumbers = numbers.filter(num => num % 2 === 0);
    console.log(evenNumbers); // [2, 4, 6, 8, 10]
    
    // Filter by string length
    const words = ['cat', 'elephant', 'dog', 'rhinoceros', 'bird'];
    const longWords = words.filter(word => word.length > 5);
    console.log(longWords); // ['elephant', 'rhinoceros']
    
    // Filter objects by property
    const products = [
        { name: 'Laptop', price: 999, inStock: true },
        { name: 'Phone', price: 599, inStock: false },
        { name: 'Tablet', price: 349, inStock: true }
    ];
    const availableProducts = products.filter(product => product.inStock);
    console.log(availableProducts); // [Laptop, Tablet]

    Real-World Examples

    // Filter search results
    const users = [
        { name: 'John Smith', age: 30, city: 'New York' },
        { name: 'Jane Doe', age: 25, city: 'Los Angeles' },
        { name: 'Bob Johnson', age: 35, city: 'New York' },
        { name: 'Alice Brown', age: 28, city: 'Chicago' }
    ];
    
    const searchUsers = (query, field) => {
        return users.filter(user => 
            user[field].toLowerCase().includes(query.toLowerCase())
        );
    };
    
    console.log(searchUsers('new york', 'city')); // Returns users from New York
    
    // Filter valid form inputs
    const formInputs = [
        { field: 'email', value: 'john@example.com' },
        { field: 'password', value: '' },
        { field: 'age', value: '25' },
        { field: 'phone', value: 'invalid' }
    ];
    
    const validInputs = formInputs.filter(input => {
        if (input.field === 'email') {
            return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value);
        }
        if (input.field === 'password') {
            return input.value.length >= 8;
        }
        if (input.field === 'age') {
            return !isNaN(input.value) && parseInt(input.value) > 0;
        }
        return true;
    });
    
    // Filter products by multiple criteria
    const inventory = [
        { id: 1, name: 'Laptop', price: 999, category: 'Electronics', rating: 4.5 },
        { id: 2, name: 'Desk', price: 199, category: 'Furniture', rating: 4.0 },
        { id: 3, name: 'Phone', price: 699, category: 'Electronics', rating: 4.8 },
        { id: 4, name: 'Chair', price: 149, category: 'Furniture', rating: 3.9 }
    ];
    
    const filterProducts = (minPrice, maxPrice, category, minRating) => {
        return inventory.filter(product => 
            product.price >= minPrice &&
            product.price <= maxPrice &&
            product.category === category &&
            product.rating >= minRating
        );
    };
    
    console.log(filterProducts(100, 500, 'Furniture', 3.5));

    Understanding reduce()

    The reduce() method executes a reducer function on each element of the array, resulting in a single output value. It's like a snowball rolling down a hill, accumulating more snow (data) as it goes.

    Basic Syntax

    // array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
    
    const result = array.reduce((accumulator, currentValue, index, array) => {
        // return updated accumulator
    }, initialValue);

    Simple Examples

    // Sum all numbers
    const numbers = [1, 2, 3, 4, 5];
    const sum = numbers.reduce((acc, num) => acc + num, 0);
    console.log(sum); // 15
    
    // Find maximum value
    const values = [10, 5, 25, 15, 30];
    const max = values.reduce((acc, val) => val > acc ? val : acc);
    console.log(max); // 30
    
    // Count occurrences
    const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
    const fruitCount = fruits.reduce((acc, fruit) => {
        acc[fruit] = (acc[fruit] || 0) + 1;
        return acc;
    }, {});
    console.log(fruitCount); // { apple: 3, banana: 2, orange: 1 }

    Advanced Examples

    // Flatten nested arrays
    const nested = [[1, 2], [3, 4], [5, 6]];
    const flattened = nested.reduce((acc, arr) => acc.concat(arr), []);
    console.log(flattened); // [1, 2, 3, 4, 5, 6]
    
    // Group objects by property
    const people = [
        { name: 'Alice', age: 25, city: 'NYC' },
        { name: 'Bob', age: 30, city: 'LA' },
        { name: 'Carol', age: 25, city: 'NYC' },
        { name: 'Dave', age: 30, city: 'LA' }
    ];
    
    const groupByAge = people.reduce((acc, person) => {
        const key = person.age;
        if (!acc[key]) {
            acc[key] = [];
        }
        acc[key].push(person);
        return acc;
    }, {});
    
    // Create a pipeline of functions
    const pipeline = [
        num => num + 1,
        num => num * 2,
        num => num - 3
    ];
    
    const result = pipeline.reduce((acc, fn) => fn(acc), 5);
    console.log(result); // ((5 + 1) * 2) - 3 = 9
    
    // Calculate shopping cart total with discounts
    const cart = [
        { name: 'Laptop', price: 999.99, quantity: 1, discount: 0.1 },
        { name: 'Mouse', price: 29.99, quantity: 2, discount: 0 },
        { name: 'Keyboard', price: 79.99, quantity: 1, discount: 0.15 }
    ];
    
    const cartTotal = cart.reduce((total, item) => {
        const itemTotal = item.price * item.quantity;
        const discountAmount = itemTotal * item.discount;
        return total + (itemTotal - discountAmount);
    }, 0);
    
    console.log(`Total: $${cartTotal.toFixed(2)}`);

    Chaining Array Methods

    These methods can be chained together to create powerful data transformations. It's like an assembly line where each station performs a specific task.

    // Example: Process and analyze product data
    const products = [
        { name: 'Laptop', price: 999, category: 'Electronics', sold: 150 },
        { name: 'Phone', price: 599, category: 'Electronics', sold: 300 },
        { name: 'Desk', price: 199, category: 'Furniture', sold: 80 },
        { name: 'Chair', price: 149, category: 'Furniture', sold: 120 },
        { name: 'Monitor', price: 299, category: 'Electronics', sold: 90 }
    ];
    
    // Find top 3 electronics products by revenue
    const topElectronics = products
        .filter(product => product.category === 'Electronics')
        .map(product => ({
            ...product,
            revenue: product.price * product.sold
        }))
        .sort((a, b) => b.revenue - a.revenue)
        .slice(0, 3)
        .map(product => `${product.name}: $${product.revenue.toLocaleString()}`);
    
    console.log(topElectronics);
    
    // Calculate category statistics
    const categoryStats = products
        .reduce((acc, product) => {
            const category = product.category;
            if (!acc[category]) {
                acc[category] = { totalRevenue: 0, itemCount: 0, totalSold: 0 };
            }
            acc[category].totalRevenue += product.price * product.sold;
            acc[category].itemCount += 1;
            acc[category].totalSold += product.sold;
            return acc;
        }, {});
    
    console.log(categoryStats);
    
    // Complex data transformation
    const users = [
        { id: 1, name: 'John', orders: [{ amount: 50 }, { amount: 75 }] },
        { id: 2, name: 'Jane', orders: [{ amount: 100 }, { amount: 50 }, { amount: 25 }] },
        { id: 3, name: 'Bob', orders: [{ amount: 200 }] }
    ];
    
    const userStats = users
        .map(user => ({
            ...user,
            totalOrders: user.orders.length,
            totalSpent: user.orders.reduce((sum, order) => sum + order.amount, 0)
        }))
        .filter(user => user.totalSpent > 100)
        .sort((a, b) => b.totalSpent - a.totalSpent)
        .map(user => ({
            name: user.name,
            averageOrderValue: (user.totalSpent / user.totalOrders).toFixed(2)
        }));
    
    console.log(userStats);

    Real-World Applications

    Data Dashboard

    // Process sales data for dashboard
    const salesData = [
        { date: '2024-01-01', product: 'Laptop', amount: 1200, region: 'North' },
        { date: '2024-01-01', product: 'Phone', amount: 800, region: 'South' },
        { date: '2024-01-02', product: 'Laptop', amount: 1500, region: 'East' },
        { date: '2024-01-02', product: 'Tablet', amount: 600, region: 'West' },
        // ... more data
    ];
    
    const dashboardData = {
        totalSales: salesData.reduce((sum, sale) => sum + sale.amount, 0),
        
        salesByRegion: salesData.reduce((acc, sale) => {
            acc[sale.region] = (acc[sale.region] || 0) + sale.amount;
            return acc;
        }, {}),
        
        topProducts: salesData
            .reduce((acc, sale) => {
                acc[sale.product] = (acc[sale.product] || 0) + sale.amount;
                return acc;
            }, {}),
        
        recentSales: salesData
            .sort((a, b) => new Date(b.date) - new Date(a.date))
            .slice(0, 5)
            .map(sale => ({
                ...sale,
                formattedDate: new Date(sale.date).toLocaleDateString(),
                formattedAmount: `$${sale.amount.toLocaleString()}`
            }))
    };

    Search and Filter System

    class ProductSearch {
        constructor(products) {
            this.products = products;
        }
        
        search(query) {
            const searchTerms = query.toLowerCase().split(' ');
            
            return this.products
                .filter(product => 
                    searchTerms.every(term => 
                        product.name.toLowerCase().includes(term) ||
                        product.description.toLowerCase().includes(term)
                    )
                )
                .map(product => ({
                    ...product,
                    relevance: this.calculateRelevance(product, searchTerms)
                }))
                .sort((a, b) => b.relevance - a.relevance);
        }
        
        calculateRelevance(product, terms) {
            return terms.reduce((score, term) => {
                if (product.name.toLowerCase().includes(term)) score += 2;
                if (product.description.toLowerCase().includes(term)) score += 1;
                return score;
            }, 0);
        }
        
        filterBySpecs(filters) {
            return this.products.filter(product => 
                Object.entries(filters).every(([key, value]) => {
                    if (Array.isArray(value)) {
                        return value.includes(product[key]);
                    }
                    return product[key] === value;
                })
            );
        }
    }

    Form Validation and Processing

    const formProcessor = {
        validators: {
            required: value => value !== '' && value !== null && value !== undefined,
            email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
            minLength: (value, length) => value.length >= length,
            maxLength: (value, length) => value.length <= length,
            numeric: value => !isNaN(value) && value !== ''
        },
        
        validateForm(formData, rules) {
            return Object.entries(rules)
                .map(([field, fieldRules]) => {
                    const value = formData[field];
                    const errors = Object.entries(fieldRules)
                        .filter(([rule, param]) => {
                            const validator = this.validators[rule];
                            return validator && !validator(value, param);
                        })
                        .map(([rule]) => `${field} failed ${rule} validation`);
                    
                    return { field, errors };
                })
                .filter(result => result.errors.length > 0)
                .reduce((acc, { field, errors }) => {
                    acc[field] = errors;
                    return acc;
                }, {});
        },
        
        processForm(formData) {
            return Object.entries(formData)
                .map(([key, value]) => {
                    if (typeof value === 'string') {
                        return [key, value.trim()];
                    }
                    return [key, value];
                })
                .reduce((acc, [key, value]) => {
                    acc[key] = value;
                    return acc;
                }, {});
        }
    };

    Performance Considerations

    // Inefficient: Multiple iterations
    const result1 = array
        .map(transform)
        .filter(condition)
        .map(anotherTransform);
    
    // More efficient: Single iteration with reduce
    const result2 = array.reduce((acc, item) => {
        const transformed = transform(item);
        if (condition(transformed)) {
            acc.push(anotherTransform(transformed));
        }
        return acc;
    }, []);
    
    // Use find() when you only need one item
    const firstMatch = array.find(item => item.id === targetId);
    
    // Use some() or every() for boolean checks
    const hasExpensiveItem = products.some(product => product.price > 1000);
    const allInStock = products.every(product => product.inStock);

    Practice Exercises

    1. Create a function that uses map to convert an array of temperatures from Celsius to Fahrenheit
    2. Write a filter function that removes all falsy values from an array
    3. Use reduce to implement your own version of the map function
    4. Chain map, filter, and reduce to process a dataset of students and calculate the average grade of passing students

    Exercise Solutions (Try First!)

    Click to see solutions
    // 1. Temperature conversion
    const celsiusToFahrenheit = (temperatures) => {
        return temperatures.map(temp => (temp * 9/5) + 32);
    };
    
    const celsius = [0, 10, 20, 30, 40];
    console.log(celsiusToFahrenheit(celsius)); // [32, 50, 68, 86, 104]
    
    // 2. Remove falsy values
    const removeFalsy = (array) => {
        return array.filter(Boolean);
        // or array.filter(item => item);
    };
    
    const mixed = [0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5];
    console.log(removeFalsy(mixed)); // [1, 2, 3, 4, 5]
    
    // 3. Implement map using reduce
    const mapWithReduce = (array, callback) => {
        return array.reduce((acc, item, index) => {
            acc.push(callback(item, index, array));
            return acc;
        }, []);
    };
    
    const numbers = [1, 2, 3, 4, 5];
    console.log(mapWithReduce(numbers, x => x * 2)); // [2, 4, 6, 8, 10]
    
    // 4. Student grade processing
    const students = [
        { name: 'Alice', grades: [85, 92, 88], status: 'active' },
        { name: 'Bob', grades: [75, 68, 72], status: 'active' },
        { name: 'Carol', grades: [92, 94, 90], status: 'active' },
        { name: 'Dave', grades: [65, 70, 68], status: 'inactive' },
        { name: 'Eve', grades: [95, 98, 92], status: 'active' }
    ];
    
    const calculatePassingAverage = (students, passingGrade = 70) => {
        return students
            .filter(student => student.status === 'active')
            .map(student => ({
                ...student,
                average: student.grades.reduce((sum, grade) => sum + grade, 0) / student.grades.length
            }))
            .filter(student => student.average >= passingGrade)
            .reduce((acc, student, index, array) => {
                acc.sum += student.average;
                if (index === array.length - 1) {
                    return acc.sum / array.length;
                }
                return acc;
            }, { sum: 0 });
    };
    
    console.log(calculatePassingAverage(students)); // Average of passing active students

    Common Patterns and Use Cases

    Data Transformation Patterns

    // Pattern 1: Object to Array conversion
    const obj = { a: 1, b: 2, c: 3 };
    const arr = Object.entries(obj).map(([key, value]) => ({ key, value }));
    
    // Pattern 2: Array to Object conversion
    const pairs = [['a', 1], ['b', 2], ['c', 3]];
    const object = pairs.reduce((acc, [key, value]) => {
        acc[key] = value;
        return acc;
    }, {});
    
    // Pattern 3: Remove duplicates
    const numbers = [1, 2, 2, 3, 4, 4, 5];
    const unique = numbers.filter((value, index, self) => 
        self.indexOf(value) === index
    );
    
    // Pattern 4: Grouping data
    const items = [
        { type: 'fruit', name: 'apple' },
        { type: 'vegetable', name: 'carrot' },
        { type: 'fruit', name: 'banana' }
    ];
    
    const grouped = items.reduce((acc, item) => {
        (acc[item.type] = acc[item.type] || []).push(item);
        return acc;
    }, {});
    
    // Pattern 5: Flatten nested structures
    const nested = [1, [2, [3, [4, 5]]]];
    const flatten = (arr) => 
        arr.reduce((acc, val) => 
            Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), 
        []);
    
    console.log(flatten(nested)); // [1, 2, 3, 4, 5]

    Summary

    These three methods form the foundation of functional programming in JavaScript:

    Key takeaways:

    Practice these methods regularly - they're used extensively in real-world JavaScript applications!