Array Methods (push, pop, shift, etc.)

Mastering JavaScript Array Manipulation Methods

Introduction to Array Methods

JavaScript arrays come with a rich set of built-in methods that make data manipulation easier and more expressive. Think of these methods as specialized tools in a toolkit - each designed for specific operations on arrays.

We can categorize array methods by their behavior and effects:

graph TD A[Array Methods] --> B[Mutator Methods] A --> C[Accessor Methods] A --> D[Iteration Methods] B --> B1[Modify original array] B --> B2["push(), pop(), shift(), unshift(), splice(), etc."] C --> C1[Return information without modifying] C --> C2["slice(), concat(), includes(), indexOf(), etc."] D --> D1[Process each element] D --> D2["forEach(), map(), filter(), reduce(), etc."] style B fill:#ffcdd2,stroke:#c62828 style C fill:#c8e6c9,stroke:#2e7d32 style D fill:#bbdefb,stroke:#1976d2

Mutator Methods - Stack Operations

A stack is a Last-In-First-Out (LIFO) data structure, and JavaScript arrays provide methods that let them behave like stacks:

Item 1 Item 2 Item 3 Item 4 pop() ⬅️ push() ➡️ Stack Operations

push() and pop()

const stack = [];

// push() adds elements to the end of the array
stack.push("First");
console.log(stack);  // ["First"]

stack.push("Second", "Third");  // Can add multiple elements
console.log(stack);  // ["First", "Second", "Third"]

// push() returns the new length of the array
const newLength = stack.push("Fourth");
console.log(newLength);  // 4
console.log(stack);     // ["First", "Second", "Third", "Fourth"]

// pop() removes and returns the last element
const lastItem = stack.pop();
console.log(lastItem);  // "Fourth"
console.log(stack);     // ["First", "Second", "Third"]

// pop() on an empty array returns undefined
const emptyArray = [];
console.log(emptyArray.pop());  // undefined

A real-world example of stack operations is undo/redo functionality in an application:

class TextEditor {
    constructor() {
        this.content = "";
        this.history = [];
        this.redoStack = [];
    }
    
    type(text) {
        this.history.push(this.content);  // Save current state for undo
        this.content += text;
        this.redoStack = [];  // Clear redo stack when new content is added
        console.log(`Current text: "${this.content}"`);
    }
    
    undo() {
        if (this.history.length === 0) {
            console.log("Nothing to undo");
            return;
        }
        
        this.redoStack.push(this.content);  // Save current state for redo
        this.content = this.history.pop();  // Restore previous state
        console.log(`After undo: "${this.content}"`);
    }
    
    redo() {
        if (this.redoStack.length === 0) {
            console.log("Nothing to redo");
            return;
        }
        
        this.history.push(this.content);  // Save current state for undo
        this.content = this.redoStack.pop();  // Restore next state
        console.log(`After redo: "${this.content}"`);
    }
}

// Using the text editor
const editor = new TextEditor();
editor.type("Hello ");        // Current text: "Hello "
editor.type("world!");        // Current text: "Hello world!"
editor.undo();               // After undo: "Hello "
editor.undo();               // After undo: ""
editor.redo();               // After redo: "Hello "
editor.type("JavaScript!");  // Current text: "Hello JavaScript!"

Mutator Methods - Queue Operations

A queue is a First-In-First-Out (FIFO) data structure, and JavaScript arrays can also behave like queues:

First Second Third Fourth Fifth shift() ⬅️ push() ➡️ Queue Operations

shift() and unshift()

const queue = [];

// Add elements to the end with push() (enqueue)
queue.push("First");
queue.push("Second");
queue.push("Third");
console.log(queue);  // ["First", "Second", "Third"]

// Remove elements from the beginning with shift() (dequeue)
const firstItem = queue.shift();
console.log(firstItem);  // "First"
console.log(queue);      // ["Second", "Third"]

// unshift() adds elements to the beginning of the array
queue.unshift("New First");
console.log(queue);  // ["New First", "Second", "Third"]

// unshift() can add multiple elements at once
queue.unshift("Even Newer", "Super New");
console.log(queue);  // ["Even Newer", "Super New", "New First", "Second", "Third"]

// unshift() returns the new length of the array
const length = queue.unshift("Zero");
console.log(length);  // 6

A classic real-world example of a queue is a print job manager:

class PrintQueue {
    constructor() {
        this.queue = [];
        this.isProcessing = false;
    }
    
    addJob(document) {
        // Add to end of queue
        this.queue.push({
            id: Date.now(),
            name: document,
            timestamp: new Date().toISOString()
        });
        console.log(`Added "${document}" to print queue. Queue length: ${this.queue.length}`);
        
        if (!this.isProcessing) {
            this.processQueue();
        }
    }
    
    processQueue() {
        if (this.queue.length === 0) {
            this.isProcessing = false;
            console.log("Print queue empty");
            return;
        }
        
        this.isProcessing = true;
        const job = this.queue.shift();  // Remove from front of queue
        
        console.log(`Printing: "${job.name}" (submitted at ${job.timestamp})`);
        
        // Simulate printing taking some time
        setTimeout(() => {
            console.log(`Finished printing: "${job.name}"`);
            this.processQueue();  // Process next job
        }, 1500);
    }
}

// Using the print queue
const printer = new PrintQueue();
printer.addJob("Resume.pdf");
printer.addJob("Report.docx");
printer.addJob("Presentation.pptx");
// Output will show jobs processing one by one

Performance Note: push() and pop() are much faster operations than shift() and unshift(), especially for large arrays. This is because adding or removing elements at the beginning requires reindexing all other elements.

Mutator Methods - splice()

The splice() method is the Swiss Army knife of array mutation - it can add, remove, and replace elements anywhere in an array:

// Basic syntax: array.splice(startIndex, deleteCount, item1, item2, ...)

const months = ['Jan', 'March', 'April', 'June'];

// Inserting elements (without removing any)
months.splice(1, 0, 'Feb');
console.log(months);  // ['Jan', 'Feb', 'March', 'April', 'June']

// Replacing elements
months.splice(4, 1, 'May');
console.log(months);  // ['Jan', 'Feb', 'March', 'April', 'May']

// Removing elements
const removed = months.splice(2, 2);
console.log(months);   // ['Jan', 'Feb', 'May']
console.log(removed);  // ['March', 'April']

// Removing elements to the end of the array
const fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry'];
fruits.splice(2);  // Remove everything from index 2 onward
console.log(fruits);  // ['apple', 'banana']

// Negative index (counts from the end)
const numbers = [1, 2, 3, 4, 5];
numbers.splice(-2, 1);  // Remove 1 element starting at the second-to-last position
console.log(numbers);   // [1, 2, 3, 5]

A real-world example of using splice() is managing a playlist:

class MusicPlaylist {
    constructor(name) {
        this.name = name;
        this.songs = [];
        this.currentIndex = 0;
    }
    
    addSong(song) {
        this.songs.push(song);
        console.log(`Added "${song}" to ${this.name} playlist`);
    }
    
    removeSong(index) {
        if (index >= 0 && index < this.songs.length) {
            const removed = this.songs.splice(index, 1)[0];
            console.log(`Removed "${removed}" from ${this.name} playlist`);
            
            // Adjust currentIndex if needed
            if (index < this.currentIndex) {
                this.currentIndex--;
            } else if (index === this.currentIndex && this.currentIndex === this.songs.length) {
                this.currentIndex = Math.max(0, this.songs.length - 1);
            }
        }
    }
    
    insertSong(index, song) {
        this.songs.splice(index, 0, song);
        console.log(`Inserted "${song}" at position ${index} in ${this.name} playlist`);
        
        // Adjust currentIndex if needed
        if (index <= this.currentIndex) {
            this.currentIndex++;
        }
    }
    
    moveSong(fromIndex, toIndex) {
        if (fromIndex >= 0 && fromIndex < this.songs.length && 
            toIndex >= 0 && toIndex < this.songs.length) {
            const song = this.songs.splice(fromIndex, 1)[0];
            this.songs.splice(toIndex, 0, song);
            console.log(`Moved "${song}" from position ${fromIndex} to ${toIndex}`);
            
            // Adjust currentIndex based on the move operation
            if (this.currentIndex === fromIndex) {
                this.currentIndex = toIndex;
            } else if (fromIndex < this.currentIndex && toIndex >= this.currentIndex) {
                this.currentIndex--;
            } else if (fromIndex > this.currentIndex && toIndex <= this.currentIndex) {
                this.currentIndex++;
            }
        }
    }
    
    getCurrentSong() {
        return this.songs[this.currentIndex];
    }
    
    displayPlaylist() {
        console.log(`\n${this.name} Playlist:`);
        this.songs.forEach((song, index) => {
            const current = index === this.currentIndex ? '▶️ ' : '  ';
            console.log(`${current}${index + 1}. ${song}`);
        });
    }
}

// Using the playlist
const workout = new MusicPlaylist("Workout Mix");
workout.addSong("Eye of the Tiger");
workout.addSong("Stronger");
workout.addSong("Thunderstruck");
workout.addSong("Can't Hold Us");
workout.displayPlaylist();

workout.removeSong(1);
workout.displayPlaylist();

workout.insertSong(1, "Till I Collapse");
workout.displayPlaylist();

workout.moveSong(2, 0);
workout.displayPlaylist();

Mutator Methods - sort()

The sort() method sorts the elements of an array in place:

// Basic sorting (default is alphabetical)
const fruits = ['banana', 'cherry', 'apple', 'date'];
fruits.sort();
console.log(fruits);  // ['apple', 'banana', 'cherry', 'date']

// CAUTION: Numbers are converted to strings by default!
const numbers = [10, 5, 100, 20, 1];
numbers.sort();
console.log(numbers);  // [1, 10, 100, 20, 5] (alphabetical, not numerical!)

// Numeric sorting with compare function
numbers.sort((a, b) => a - b);  // Ascending order
console.log(numbers);  // [1, 5, 10, 20, 100]

numbers.sort((a, b) => b - a);  // Descending order
console.log(numbers);  // [100, 20, 10, 5, 1]

// Sorting objects
const products = [
    { name: "Laptop", price: 999 },
    { name: "Phone", price: 699 },
    { name: "Tablet", price: 399 },
    { name: "Headphones", price: 199 }
];

// Sort by price (ascending)
products.sort((a, b) => a.price - b.price);
console.log(products);
// [
//   { name: "Headphones", price: 199 },
//   { name: "Tablet", price: 399 },
//   { name: "Phone", price: 699 },
//   { name: "Laptop", price: 999 }
// ]

// Sort by name (alphabetical)
products.sort((a, b) => a.name.localeCompare(b.name));
console.log(products);
// [
//   { name: "Headphones", price: 199 },
//   { name: "Laptop", price: 999 },
//   { name: "Phone", price: 699 },
//   { name: "Tablet", price: 399 }
// ]

More Complex Sorting

// Custom case-insensitive sort
const names = ['Daniel', 'alex', 'Beth', 'charlie'];
names.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(names);  // ['alex', 'Beth', 'charlie', 'Daniel']

// Multi-field sort (primary, secondary criteria)
const students = [
    { name: "Alice", grade: "A", score: 95 },
    { name: "Bob", grade: "B", score: 85 },
    { name: "Charlie", grade: "A", score: 92 },
    { name: "David", grade: "B", score: 88 },
    { name: "Eve", grade: "A", score: 91 }
];

// Sort by grade first, then by score (descending)
students.sort((a, b) => {
    // Compare grades
    const gradeComparison = a.grade.localeCompare(b.grade);
    
    // If grades are the same, compare scores
    if (gradeComparison === 0) {
        return b.score - a.score;  // Descending score
    }
    
    return gradeComparison;
});

console.log(students);
// Output:
// [
//   { name: "Alice", grade: "A", score: 95 },
//   { name: "Charlie", grade: "A", score: 92 },
//   { name: "Eve", grade: "A", score: 91 },
//   { name: "David", grade: "B", score: 88 },
//   { name: "Bob", grade: "B", score: 85 }
// ]

Note: sort() modifies the original array. If you need to keep the original order, make a copy first using methods like slice() or the spread operator.

Other Mutator Methods

reverse()

const numbers = [1, 2, 3, 4, 5];
numbers.reverse();
console.log(numbers);  // [5, 4, 3, 2, 1]

// Common pattern: Sort and reverse for descending order
const fruits = ['banana', 'cherry', 'apple', 'date'];
fruits.sort().reverse();
console.log(fruits);  // ['date', 'cherry', 'banana', 'apple']

fill()

// Fill entire array with a value
const zeros = new Array(5).fill(0);
console.log(zeros);  // [0, 0, 0, 0, 0]

// Fill a portion of the array
const numbers = [1, 2, 3, 4, 5];
numbers.fill(0, 2, 4);  // Fill with 0 from index 2 up to (but not including) index 4
console.log(numbers);  // [1, 2, 0, 0, 5]

// Fill with object references (be careful!)
const array = new Array(3).fill({});
array[0].name = "One";
console.log(array);  // [{ name: "One" }, { name: "One" }, { name: "One" }]
// All elements reference the same object!

copyWithin()

// Syntax: array.copyWithin(target, start, end)
const array = [1, 2, 3, 4, 5];

// Copy elements from index 3 to index 4 (inclusive) to index 0
array.copyWithin(0, 3, 5);
console.log(array);  // [4, 5, 3, 4, 5]

// More examples
const letters = ['a', 'b', 'c', 'd', 'e'];
letters.copyWithin(1, 3);  // Copy from index 3 to end, starting at index 1
console.log(letters);  // ['a', 'd', 'e', 'd', 'e']

Accessor Methods - Non-Mutating Operations

Accessor methods don't modify the original array but return a new array or value.

slice()

// Syntax: array.slice(startIndex, endIndex)
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

console.log(animals.slice(2));        // ['camel', 'duck', 'elephant']
console.log(animals.slice(2, 4));     // ['camel', 'duck']
console.log(animals.slice(1, 5));     // ['bison', 'camel', 'duck', 'elephant']
console.log(animals.slice(-2));       // ['duck', 'elephant']
console.log(animals.slice(2, -1));    // ['camel', 'duck']

// Original array remains unchanged
console.log(animals);  // ['ant', 'bison', 'camel', 'duck', 'elephant']

// Common use: Create a shallow copy of an array
const copy = animals.slice();
console.log(copy);  // ['ant', 'bison', 'camel', 'duck', 'elephant']

concat()

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = ['g', 'h', 'i'];

// Combine arrays
const combined = array1.concat(array2, array3);
console.log(combined);  // ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

// Original arrays remain unchanged
console.log(array1);  // ['a', 'b', 'c']

// Concat can also take values in addition to arrays
const combined2 = array1.concat('d', ['e', 'f'], [['g', 'h']]);
console.log(combined2);  // ['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h']]

// Modern alternative using spread operator
const spreadCombined = [...array1, ...array2, ...array3];
console.log(spreadCombined);  // ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

join()

const elements = ['Fire', 'Air', 'Water'];

console.log(elements.join());      // 'Fire,Air,Water'
console.log(elements.join(''));    // 'FireAirWater'
console.log(elements.join('-'));   // 'Fire-Air-Water'
console.log(elements.join(' + ')); // 'Fire + Air + Water'

// Common use: Creating CSV data
const rows = [
    ['John', 32, 'New York'],
    ['Sarah', 27, 'Los Angeles'],
    ['Mike', 41, 'Chicago']
];

const csv = rows.map(row => row.join(',')).join('\n');
console.log(csv);
// Output:
// John,32,New York
// Sarah,27,Los Angeles
// Mike,41,Chicago

toString() and toLocaleString()

const array = [1, 2, 'a', '1a'];

console.log(array.toString());  // '1,2,a,1a'

// toLocaleString applies locale-specific formatting to elements
const prices = [1000, 2000, 3000];
console.log(prices.toLocaleString('en-US', { style: 'currency', currency: 'USD' }));
// '$1,000.00,$2,000.00,$3,000.00'

// Dates are formatted according to locale
const dates = [new Date('2023-01-01'), new Date('2023-06-15')];
console.log(dates.toLocaleString('en-US'));
// '1/1/2023, 12:00:00 AM,6/15/2023, 12:00:00 AM'
console.log(dates.toLocaleString('de-DE'));
// '1.1.2023, 00:00:00,15.6.2023, 00:00:00'

Search Methods

indexOf() and lastIndexOf()

const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];

console.log(beasts.indexOf('bison'));        // 1
console.log(beasts.indexOf('bison', 2));     // 4 (start from index 2)
console.log(beasts.indexOf('giraffe'));      // -1 (not found)

console.log(beasts.lastIndexOf('bison'));    // 4 (search from the end)
console.log(beasts.lastIndexOf('bison', 3)); // 1 (search from index 3 toward the beginning)

// Common pattern: Check if an element exists
const containsDuck = beasts.indexOf('duck') !== -1;
console.log(containsDuck);  // true

// Modern alternative to check existence
const containsCamel = beasts.includes('camel');
console.log(containsCamel);  // true

includes()

const array = [1, 2, 3];

console.log(array.includes(2));        // true
console.log(array.includes(4));        // false
console.log(array.includes(1, 1));     // false (start from index 1)

// Works with strings and other types
const pets = ['cat', 'dog', 'bat'];
console.log(pets.includes('cat'));     // true
console.log(pets.includes('at'));      // false (exact match required)

// NaN handling is better than indexOf
const numbers = [1, 2, NaN, 3, 4];
console.log(numbers.indexOf(NaN));     // -1 (indexOf can't find NaN)
console.log(numbers.includes(NaN));    // true (includes can find NaN)

find() and findIndex()

const inventory = [
    { name: 'apples', quantity: 2 },
    { name: 'bananas', quantity: 0 },
    { name: 'cherries', quantity: 5 }
];

// find() returns the first element that satisfies the condition
const result = inventory.find(item => item.name === 'cherries');
console.log(result);  // { name: 'cherries', quantity: 5 }

// findIndex() returns the index of the first matching element
const index = inventory.findIndex(item => item.quantity === 0);
console.log(index);  // 1

// If no matching element is found
const nothingFound = inventory.find(item => item.name === 'oranges');
console.log(nothingFound);  // undefined

const noIndex = inventory.findIndex(item => item.quantity > 10);
console.log(noIndex);  // -1

findLast() and findLastIndex() (ES2023)

const numbers = [5, 12, 8, 130, 44, 4, 18];

// Find last number less than 20
const lastSmall = numbers.findLast(num => num < 20);
console.log(lastSmall);  // 18

// Find index of last number less than 20
const lastSmallIndex = numbers.findLastIndex(num => num < 20);
console.log(lastSmallIndex);  // 6

Real-World Array Method Applications

Shopping Cart Implementation

class ShoppingCart {
    constructor() {
        this.items = [];
        this.discounts = [];
    }
    
    addItem(product, quantity = 1) {
        const existingItem = this.items.find(item => item.product.id === product.id);
        
        if (existingItem) {
            existingItem.quantity += quantity;
            console.log(`Updated quantity of ${product.name} to ${existingItem.quantity}`);
        } else {
            this.items.push({
                product,
                quantity,
                addedAt: new Date()
            });
            console.log(`Added ${quantity} ${product.name} to cart`);
        }
    }
    
    removeItem(productId) {
        const index = this.items.findIndex(item => item.product.id === productId);
        
        if (index !== -1) {
            const [removedItem] = this.items.splice(index, 1);
            console.log(`Removed ${removedItem.product.name} from cart`);
        }
    }
    
    updateQuantity(productId, newQuantity) {
        const item = this.items.find(item => item.product.id === productId);
        
        if (item) {
            if (newQuantity <= 0) {
                this.removeItem(productId);
            } else {
                item.quantity = newQuantity;
                console.log(`Updated ${item.product.name} quantity to ${newQuantity}`);
            }
        }
    }
    
    addDiscount(code, percentage) {
        if (!this.discounts.find(d => d.code === code)) {
            this.discounts.push({ code, percentage });
            console.log(`Added discount ${code} for ${percentage}% off`);
        }
    }
    
    applyDiscount(code) {
        const discount = this.discounts.find(d => d.code === code);
        return discount ? discount.percentage : 0;
    }
    
    getTotal() {
        return this.items.reduce((total, item) => {
            return total + (item.product.price * item.quantity);
        }, 0);
    }
    
    getTotalWithDiscount(discountCode) {
        const total = this.getTotal();
        const discountPercentage = this.applyDiscount(discountCode);
        return total * (1 - discountPercentage / 100);
    }
    
    getItemsGroupedByCategory() {
        const grouped = {};
        
        for (const item of this.items) {
            const category = item.product.category;
            
            if (!grouped[category]) {
                grouped[category] = [];
            }
            
            grouped[category].push(item);
        }
        
        return grouped;
    }
    
    displayCart() {
        if (this.items.length === 0) {
            console.log("Your cart is empty");
            return;
        }
        
        console.log("\n===== SHOPPING CART =====");
        this.items.forEach(item => {
            console.log(
                `${item.product.name} - $${item.product.price.toFixed(2)} × ${item.quantity} = $${(item.product.price * item.quantity).toFixed(2)}`
            );
        });
        console.log("=======================");
        console.log(`Subtotal: $${this.getTotal().toFixed(2)}`);
    }
}

// Using the shopping cart
const cart = new ShoppingCart();

const products = [
    { id: 1, name: "Laptop", price: 999.99, category: "Electronics" },
    { id: 2, name: "Headphones", price: 89.99, category: "Electronics" },
    { id: 3, name: "Keyboard", price: 59.99, category: "Electronics" },
    { id: 4, name: "Coffee Mug", price: 12.99, category: "Home Goods" },
    { id: 5, name: "T-Shirt", price: 19.99, category: "Clothing" }
];

cart.addItem(products[0]);
cart.addItem(products[1], 2);
cart.addItem(products[3]);
cart.displayCart();

cart.addDiscount("SAVE20", 20);
console.log(`Total with discount: $${cart.getTotalWithDiscount("SAVE20").toFixed(2)}`);

cart.updateQuantity(1, 2);
cart.removeItem(4);
cart.displayCart();

Task Manager Application

class TaskManager {
    constructor() {
        this.tasks = [];
        this.lastId = 0;
    }
    
    addTask(title, description = "", dueDate = null, priority = "medium") {
        const newTask = {
            id: ++this.lastId,
            title,
            description,
            dueDate: dueDate ? new Date(dueDate) : null,
            priority,
            completed: false,
            createdAt: new Date(),
            tags: []
        };
        
        this.tasks.push(newTask);
        return newTask.id;
    }
    
    deleteTask(id) {
        const index = this.tasks.findIndex(task => task.id === id);
        if (index !== -1) {
            this.tasks.splice(index, 1);
            return true;
        }
        return false;
    }
    
    updateTask(id, updates) {
        const task = this.tasks.find(task => task.id === id);
        if (!task) return false;
        
        // Apply updates
        Object.keys(updates).forEach(key => {
            if (key in task && key !== 'id') {
                task[key] = updates[key];
            }
        });
        
        return true;
    }
    
    completeTask(id) {
        return this.updateTask(id, { completed: true });
    }
    
    addTag(taskId, tag) {
        const task = this.tasks.find(task => task.id === taskId);
        if (!task) return false;
        
        if (!task.tags.includes(tag)) {
            task.tags.push(tag);
        }
        return true;
    }
    
    removeTag(taskId, tag) {
        const task = this.tasks.find(task => task.id === taskId);
        if (!task) return false;
        
        const tagIndex = task.tags.indexOf(tag);
        if (tagIndex !== -1) {
            task.tags.splice(tagIndex, 1);
            return true;
        }
        return false;
    }
    
    getTaskById(id) {
        return this.tasks.find(task => task.id === id);
    }
    
    getAllTasks() {
        return this.tasks.slice(); // Return a copy
    }
    
    getCompletedTasks() {
        return this.tasks.filter(task => task.completed);
    }
    
    getPendingTasks() {
        return this.tasks.filter(task => !task.completed);
    }
    
    getTasksByPriority(priority) {
        return this.tasks.filter(task => task.priority === priority);
    }
    
    getTasksByTag(tag) {
        return this.tasks.filter(task => task.tags.includes(tag));
    }
    
    getOverdueTasks() {
        const now = new Date();
        return this.tasks.filter(task => 
            !task.completed && 
            task.dueDate && 
            task.dueDate < now
        );
    }
    
    getSortedTasks(sortBy = 'dueDate', ascending = true) {
        const sortedTasks = this.tasks.slice(); // Create a copy
        
        sortedTasks.sort((a, b) => {
            let valueA = a[sortBy];
            let valueB = b[sortBy];
            
            // Handle null values for dates
            if (sortBy === 'dueDate') {
                if (!valueA && !valueB) return 0;
                if (!valueA) return ascending ? 1 : -1;
                if (!valueB) return ascending ? -1 : 1;
            }
            
            // Comparison
            if (valueA < valueB) return ascending ? -1 : 1;
            if (valueA > valueB) return ascending ? 1 : -1;
            return 0;
        });
        
        return sortedTasks;
    }
    
    summarizeTasks() {
        const total = this.tasks.length;
        const completed = this.getCompletedTasks().length;
        const pending = total - completed;
        const overdue = this.getOverdueTasks().length;
        
        const priorityCounts = {
            high: this.getTasksByPriority('high').length,
            medium: this.getTasksByPriority('medium').length,
            low: this.getTasksByPriority('low').length
        };
        
        // Get all unique tags
        const allTags = new Set();
        this.tasks.forEach(task => {
            task.tags.forEach(tag => allTags.add(tag));
        });
        
        return {
            total,
            completed,
            pending,
            overdue,
            priorityCounts,
            tags: Array.from(allTags)
        };
    }
    
    displayTasks(tasks = this.tasks) {
        if (tasks.length === 0) {
            console.log("No tasks to display");
            return;
        }
        
        console.log("\n===== TASKS =====");
        tasks.forEach(task => {
            const status = task.completed ? "✅" : "⬜";
            const priority = {
                high: "🔴",
                medium: "🟡",
                low: "🟢"
            }[task.priority] || "⚪";
            
            console.log(`${status} ${priority} [${task.id}] ${task.title}`);
            
            if (task.description) {
                console.log(`   ${task.description}`);
            }
            
            if (task.dueDate) {
                console.log(`   Due: ${task.dueDate.toLocaleDateString()}`);
            }
            
            if (task.tags.length > 0) {
                console.log(`   Tags: ${task.tags.join(', ')}`);
            }
            
            console.log('----------------');
        });
    }
}

// Using the task manager
const manager = new TaskManager();

manager.addTask("Complete project", "Finish the JavaScript course project", "2025-06-01", "high");
manager.addTask("Buy groceries", "Milk, eggs, bread", "2025-05-15", "medium");
manager.addTask("Call mom", "", "2025-05-10", "medium");
manager.addTask("Read book", "JavaScript: The Good Parts", null, "low");

manager.addTag(1, "work");
manager.addTag(1, "coding");
manager.addTag(2, "personal");
manager.addTag(3, "personal");
manager.addTag(4, "learning");
manager.addTag(4, "coding");

manager.completeTask(3);

console.log("All tasks:");
manager.displayTasks();

console.log("\nTask summary:", manager.summarizeTasks());

console.log("\nTasks sorted by priority (high to low):");
manager.displayTasks(manager.getSortedTasks('priority', false));

console.log("\nTasks with 'coding' tag:");
manager.displayTasks(manager.getTasksByTag('coding'));

Practice Activities

Activity 1: Array Method Mastery

Create a function that takes an array of numbers and performs multiple operations on it using different array methods. For example, filter out negative numbers, double the remaining values, sort them in descending order, and return only the top 3 values.

Activity 2: Recipe Book Manager

Implement a recipe management system using arrays and array methods. Allow adding, removing, and searching recipes based on ingredients, categories, or cooking time.

Activity 3: Custom Array Methods

Create your own implementations of common array methods (like map, filter, and reduce) to understand how they work internally. Then compare your implementations with the built-in methods.

Summary

In this lecture, we've explored:

Mastering array methods is essential for effective JavaScript programming. These methods provide powerful, expressive ways to work with collections of data and are fundamental tools in your developer toolkit.