Loops
Let's learn the best practices for writing loops.
Key points
- Use higher-order functions instead of for loops
- Choose between forEach, map, filter, and reduce
- Understand the difference between for...of and for...in
- Pick the appropriate loop construct
Use higher-order functions instead of for loops
JavaScript provides higher-order functions for manipulating arrays (forEach, map, filter, reduce, and more). Using them lets you write simpler code with clearer intent.
Bad
// ❌ Looping with a for statement
const numbers: number[] = [1, 2, 3, 4, 5];
const doubled: number[] = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
Good
// ✅ Using map
const numbers: number[] = [1, 2, 3, 4, 5];
const doubled: number[] = numbers.map(num => num * 2);
How to use forEach
forEach is a higher-order function that runs a process for each element of an array. It has no return value.
// forEach basics
const fruits: string[] = ['apple', 'banana', 'orange'];
// ❌ for statement
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// ✅ forEach
fruits.forEach(fruit => {
console.log(fruit);
});
// When you need the index
fruits.forEach((fruit, index) => {
console.log(`${index + 1}. ${fruit}`);
});
Caveat
forEach cannot stop midway. When you need to break out, use for...of or some/every.
// ❌ forEach cannot break out midway (break is not available)
const numbers: number[] = [1, 2, 3, 4, 5];
numbers.forEach(num => {
if (num === 3) return; // This only moves to the next element
console.log(num);
});
// Output: 1, 2, 4, 5
// ✅ When you want to break out midway, use for...of
for (const num of numbers) {
if (num === 3) break;
console.log(num);
}
// Output: 1, 2
// ✅ Or use some/every
numbers.some(num => {
if (num === 3) return true; // Returning true stops the iteration
console.log(num);
return false;
});
// Output: 1, 2
How to use map
map transforms each element of an array and produces a new array.
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: 'Taro', email: 'taro@example.com' },
{ id: 2, name: 'Hanako', email: 'hanako@example.com' },
];
// ❌ for statement
const names: string[] = [];
for (let i = 0; i < users.length; i++) {
names.push(users[i].name);
}
// ✅ map
const userNames: string[] = users.map(user => user.name);
// ['Taro', 'Hanako']
// Transforming objects
interface UserSummary {
id: number;
displayName: string;
}
const summaries: UserSummary[] = users.map(user => ({
id: user.id,
displayName: `${user.name} (${user.email})`,
}));
How to use filter
filter extracts only the elements that match a condition and produces a new array.
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
const products: Product[] = [
{ id: 1, name: 'Apple', price: 100, inStock: true },
{ id: 2, name: 'Banana', price: 80, inStock: false },
{ id: 3, name: 'Orange', price: 120, inStock: true },
];
// ❌ for statement
const inStockProducts: Product[] = [];
for (let i = 0; i < products.length; i++) {
if (products[i].inStock) {
inStockProducts.push(products[i]);
}
}
// ✅ filter
const availableProducts: Product[] = products.filter(product => product.inStock);
// Multiple conditions
const affordableAndAvailable: Product[] = products.filter(
product => product.inStock && product.price <= 100
);
// Combining with a type guard
const values: (string | null)[] = ['a', null, 'b', null, 'c'];
const strings: string[] = values.filter((v): v is string => v !== null);
How to use reduce
reduce aggregates an array into a single value.
const numbers: number[] = [1, 2, 3, 4, 5];
// Calculate the sum
const sum: number = numbers.reduce((acc, num) => acc + num, 0);
// 15
// Aggregate into an object
interface Item {
category: string;
name: string;
}
const items: Item[] = [
{ category: 'fruit', name: 'Apple' },
{ category: 'vegetable', name: 'Carrot' },
{ category: 'fruit', name: 'Banana' },
];
const grouped = items.reduce<Record<string, string[]>>((acc, item) => {
if (!acc[item.category]) {
acc[item.category] = [];
}
acc[item.category].push(item.name);
return acc;
}, {});
// { fruit: ['Apple', 'Banana'], vegetable: ['Carrot'] }
Use method chaining
Higher-order functions can be chained to express complex processing step by step.
interface Transaction {
id: number;
type: 'income' | 'expense';
amount: number;
date: Date;
}
const transactions: Transaction[] = [
{ id: 1, type: 'income', amount: 1000, date: new Date('2024-01-01') },
{ id: 2, type: 'expense', amount: 500, date: new Date('2024-01-02') },
{ id: 3, type: 'income', amount: 2000, date: new Date('2024-01-03') },
{ id: 4, type: 'expense', amount: 300, date: new Date('2024-01-04') },
];
// Calculate total income
const totalIncome = transactions
.filter(t => t.type === 'income')
.map(t => t.amount)
.reduce((sum, amount) => sum + amount, 0);
// 3000
// Get the list of expenses for January 2024
const januaryExpenses = transactions
.filter(t => t.type === 'expense')
.filter(t => t.date.getMonth() === 0 && t.date.getFullYear() === 2024)
.map(t => ({ id: t.id, amount: t.amount }));
The difference between for...of and for...in
for...of
Iterates over the values of an array.
const fruits: string[] = ['apple', 'banana', 'orange'];
for (const fruit of fruits) {
console.log(fruit);
}
// apple, banana, orange
// Works with Map and Set too
const map = new Map<string, number>([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
for...in
Iterates over the keys of an object.
const person = { name: 'Taro', age: 25, city: 'Tokyo' };
for (const key in person) {
console.log(`${key}: ${person[key as keyof typeof person]}`);
}
// name: Taro, age: 25, city: Tokyo
// ⚠️ Better not to use it for arrays
const arr = ['a', 'b', 'c'];
for (const index in arr) {
console.log(index); // '0', '1', '2' (strings)
}
Choosing between them
// ✅ Process array values → for...of
const numbers: number[] = [1, 2, 3];
for (const num of numbers) {
console.log(num);
}
// ✅ Process object keys → for...in or Object.keys/entries
const obj = { a: 1, b: 2 };
// for...in
for (const key in obj) {
console.log(key, obj[key as keyof typeof obj]);
}
// Object.entries (recommended)
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
Exercises
Rewrite the following for statements using higher-order functions
// Problem 1: rewrite with forEach
const cities: string[] = ['Tokyo', 'Osaka', 'Kyoto'];
for (let i = 0; i < cities.length; i++) {
console.log(cities[i]);
}
// Problem 2: rewrite with map
const prices: number[] = [100, 200, 300];
const taxIncluded: number[] = [];
for (let i = 0; i < prices.length; i++) {
taxIncluded.push(prices[i] * 1.1);
}
// Problem 3: rewrite with filter
const scores: number[] = [85, 42, 93, 67, 38, 79];
const passing: number[] = [];
for (let i = 0; i < scores.length; i++) {
if (scores[i] >= 60) {
passing.push(scores[i]);
}
}
Answer:
// Problem 1: forEach
const cities: string[] = ['Tokyo', 'Osaka', 'Kyoto'];
cities.forEach(city => console.log(city));
// Problem 2: map
const prices: number[] = [100, 200, 300];
const taxIncluded: number[] = prices.map(price => price * 1.1);
// Problem 3: filter
const scores: number[] = [85, 42, 93, 67, 38, 79];
const passing: number[] = scores.filter(score => score >= 60);
Summary of loops
- Simple iteration →
forEach - Transformation →
map - Extraction →
filter - Aggregation →
reduce - Array values →
for...of - Object keys →
Object.entries - Breaking out midway →
for...of+break - Express complex processing with method chaining
What to read next
- Conditionals — keep branches inside loops readable
- Function design — extract loop processing into functions