Week 6 - Advanced topics

Synchronous File IO

File processing

Error handling

Array methods

Closures

Style - How to write meaningful comments

Practice

Assignment

Core program

Understanding Array Methods

You've already learned basic array operations like push(), pop(), and slice(). Now it's time to level up with array methods: powerful functions that transform how you work with collections of data.

Think about what you currently do with loops:

// Find all products over €50
const expensive = [];
for (let i = 0; i < products.length; i++) {
  if (products[i].price > 50) {
    expensive.push(products[i]);
  }
}

// Double all prices
const doubled = [];
for (let i = 0; i < prices.length; i++) {
  doubled.push(prices[i] * 2);
}

// Calculate total
let total = 0;
for (let i = 0; i < prices.length; i++) {
  total += prices[i];
}

Array methods let you express these operations declaratively: you describe what you want, not how to get it:

// Find all products over €50
const expensive = products.filter(p => p.price > 50);

// Double all prices
const doubled = prices.map(price => price * 2);

// Calculate total
const total = prices.reduce((sum, price) => sum + price, 0);

Same results, dramatically less code. More importantly, the intent is crystal clear.

These methods are fundamental to modern JavaScript. They're used everywhere: frontend frameworks like React, backend code in Node.js, data processing. Master them and you'll write cleaner, more maintainable code that other developers instantly understand.

<aside> 💡

Array methods don't modify the original array (except sort()). They return new arrays, making your code safer and more predictable. This concept is called "immutability" and is a cornerstone of modern JavaScript.

</aside>

https://www.youtube.com/watch?v=R8rmfD9Y5-c

What you'll learn:

map() - Transform Each Element

Creates a new array by applying a function to each element.

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

// Extract properties
const users = [
  { name: 'Alice', age: 28 },
  { name: 'Bob', age: 32 }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob']

// Transform objects
const withIds = users.map((user, index) => ({
  id: index + 1,
  ...user
}));

Callback parameters:

const numbered = ['a', 'b', 'c'].map((letter, index) => {
  return `${index + 1}. ${letter}`;
});
// ['1. a', '2. b', '3. c']

<aside> 💡

map() always returns a new array with the same length as the original.

</aside>

forEach() - Call a function once for each array element

Executes a function for each array element. Unlike map(), it doesn't return a new array: it's purely for side effects like logging or updating external state.

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

// Simple iteration
numbers.forEach(num => {
  console.log(num);
});
// 1
// 2
// 3
// 4

// With index
const fruits = ['apple', 'banana', 'orange'];
fruits.forEach((fruit, index) => {
  console.log(`${index + 1}. ${fruit}`);
});
// 1. apple
// 2. banana
// 3. orange

Common use cases

// Update external variable
let sum = 0;
numbers.forEach(num => {
  sum += num;
});
console.log(sum); // 10

// Update DOM elements (in browser)
userElements.forEach(element => {
  element.classList.add('active');
});

// Logging or debugging
users.forEach(user => {
  console.log(`Processing user: ${user.name}`);
  processUser(user);
});

<aside> 💡

Use forEach() when you need to perform an action for each element but don't need a new array. If you need to transform data, use map() instead.

</aside>

filter() - Keep Matching Elements

Creates a new array with elements that pass a test.

const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6]

// Filter objects
const users = [
  { name: 'Alice', age: 28, active: true },
  { name: 'Bob', age: 32, active: false },
  { name: 'Charlie', age: 25, active: true }
];

const activeUsers = users.filter(user => user.active);
const adults = users.filter(user => user.age >= 30);

Common patterns

// Remove empty strings
const cleaned = data.filter(str => str.trim() !== '');

// Remove duplicates (with indexOf)
const unique = array.filter((item, index) => 
  array.indexOf(item) === index
);

// Complex conditions
const filtered = products.filter(product => 
  product.price > 20 && product.inStock && product.category === 'electronics'
);

find() - Get First Match

Returns the first element that passes the test (or undefined).

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: 'Bob' }

const missing = users.find(u => u.id === 99);
console.log(missing); // undefined

Use cases

// Find by property
const admin = users.find(u => u.role === 'admin');

// Find with complex logic
const eligible = applicants.find(a => 
  a.age >= 18 && a.hasLicense && !a.hasCriminalRecord
);

// Check if found
if (user) {
  console.log('Found:', user.name);
} else {
  console.log('User not found');
}

<aside> 💡

Use find() when you need the element itself. Use findIndex() if you need its position.

</aside>

every() - Test All Elements

Returns true if all elements pass the test.

const numbers = [2, 4, 6, 8];
const allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // true

const hasOdd = [2, 3, 4].every(num => num % 2 === 0);
console.log(hasOdd); // false

Validation

const users = [
  { name: 'Alice', age: 28 },
  { name: 'Bob', age: 32 },
  { name: 'Charlie', age: 25 }
];

const allAdults = users.every(user => user.age >= 18);
const allHaveNames = users.every(user => user.name && user.name.length > 0);

if (allAdults && allHaveNames) {
  console.log('All users valid');
}

some() - Test Any Element

Returns true if at least one element passes the test.

const numbers = [1, 3, 5, 8];
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true

const allOdd = [1, 3, 5].some(num => num % 2 === 0);
console.log(allOdd); // false

Common uses

// Check if any user is admin
const hasAdmin = users.some(user => user.role === 'admin');

// Check if any item out of stock
const hasOutOfStock = products.some(p => p.stock === 0);

// Check if any error in validation
const hasErrors = validationResults.some(result => !result.valid);

<aside> ⌨️

Hands on: Given an array of numbers, use every() to check if all are positive, and some() to check if any are divisible by 10.

</aside>

sort() - Reorder Elements

// Numbers - WRONG (converts to strings)
const numbers = [10, 2, 30, 4];
numbers.sort();
console.log(numbers); // [10, 2, 30, 4] - alphabetical!

// Numbers - CORRECT (with compare function)
numbers.sort((a, b) => a - b);
console.log(numbers); // [2, 4, 10, 30]

// Descending
numbers.sort((a, b) => b - a);
console.log(numbers); // [30, 10, 4, 2]

// Strings (works by default)
const words = ['banana', 'apple', 'cherry'];
words.sort();
console.log(words); // ['apple', 'banana', 'cherry']

Compare function:

// Sort objects by property
const users = [
  { name: 'Charlie', age: 25 },
  { name: 'Alice', age: 28 },
  { name: 'Bob', age: 32 }
];

// By age
users.sort((a, b) => a.age - b.age);

// By name
users.sort((a, b) => a.name.localeCompare(b.name));

// Multiple criteria
users.sort((a, b) => {
  if (a.active !== b.active) {
    return a.active ? -1 : 1; // Active users first
  }
  return a.name.localeCompare(b.name); // Then by name
});

<aside> ⚠️

sort() modifies the original array in place. To keep the original, copy first: [...array].sort()

</aside>

reduce() - Combine to Single Value

Most powerful but complex. Reduces array to a single value by applying a function.

// Sum numbers
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((total, num) => total + num, 0);
console.log(sum); // 10

// Find maximum
const max = numbers.reduce((max, num) => num > max ? num : max, numbers[0]);

// Count occurrences
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const counts = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
// { apple: 3, banana: 2, orange: 1 }

// As an alternative, you can also use loops.
// Count occurrences (for...of)
const counts = {};
for (const fruit of fruits) {
  counts[fruit] = (counts[fruit] || 0) + 1;
}

Tradeoffs between reduce and loops: reduce is concise and expressive for aggregations once you’re familiar with it, and it’s common in functional-style codebases. for...of is more explicit and often easier to read for beginners, with slightly less overhead; both have the same time and space complexity, so the choice is mainly about clarity and team conventions.

Parameters:

// Build object from array
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];

const usersById = users.reduce((acc, user) => {
  acc[user.id] = user;
  return acc;
}, {});
// { 1: { id: 1, name: 'Alice' }, 2: { id: 2, name: 'Bob' } }

// Flatten nested arrays
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5]

// Group by property
const products = [
  { name: 'Laptop', category: 'electronics' },
  { name: 'Shirt', category: 'clothing' },
  { name: 'Phone', category: 'electronics' }
];

const grouped = products.reduce((acc, product) => {
  const category = product.category;
  if (!acc[category]) {
    acc[category] = [];
  }
  acc[category].push(product);
  return acc;
}, {});
// { electronics: [...], clothing: [...] }

<aside> 💡

</aside>