| name | functional-constructs-types |
| description | Use when building arrays in loops. Use when types don't flow through code. Use when considering map/filter/reduce. |
Use Functional Constructs and Libraries to Help Types Flow
Overview
Functional programming constructs (map, filter, reduce) work better with TypeScript than imperative loops.
TypeScript's type inference works particularly well with functional constructs. They produce intermediate types that flow naturally, whereas loops require you to manually track types.
When to Use This Skill
- Building arrays with for loops
- Transforming data structures
- Types not flowing through imperative code
- Choosing between loops and functional methods
The Iron Rule
Prefer map, filter, and reduce over for loops.
Types flow naturally through functional chains.
Remember:
- Functional methods return typed values
- Loops require manual type management
- Chaining preserves type context
- Libraries like Lodash have excellent type support
Detection: Loop Type Problems
const result: string[] = [];
for (const item of items) {
result.push(item.name);
}
const result = [];
for (const item of items) {
result.push(item.name);
}
result
The Functional Solution
const result = items.map(item => item.name);
TypeScript infers the output type from the input type and the mapping function.
Type Flow Through Chains
const namesOfAdults = people
.filter(p => p.age >= 18)
.map(p => p.name)
.sort()
.join(', ');
Each step has a well-defined type that TypeScript tracks.
Common Transformations
map: Transform Each Element
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
const users = [{ name: 'Alice', age: 30 }];
const names = users.map(u => u.name);
filter: Keep Elements Matching Condition
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);
const mixed: (string | number)[] = [1, 'a', 2, 'b'];
const strings = mixed.filter((x): x is string => typeof x === 'string');
reduce: Aggregate to Single Value
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, n) => acc + n, 0);
const grouped = items.reduce((acc, item) => {
const key = item.category;
acc[key] = acc[key] || [];
acc[key].push(item);
return acc;
}, {} as Record<string, Item[]>);
flatMap: Map and Flatten
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.flatMap(arr => arr);
const sentences = ['Hello world', 'TypeScript rocks'];
const words = sentences.flatMap(s => s.split(' '));
Object Transformations
Object.entries / Object.fromEntries
const obj = { a: 1, b: 2, c: 3 };
const doubled = Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, v * 2])
);
const filtered = Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v > 1)
);
Record Transformations
type Input = Record<string, number>;
type Output = Record<string, string>;
const input: Input = { a: 1, b: 2 };
const output: Output = Object.fromEntries(
Object.entries(input).map(([k, v]) => [k, String(v)])
);
Lodash and Type-Friendly Libraries
import _ from 'lodash';
const grouped = _.groupBy(users, 'department');
const sorted = _.sortBy(users, ['lastName', 'firstName']);
const unique = _.uniqBy(users, 'id');
Lodash has excellent TypeScript support.
When Loops Are OK
Performance-Critical Code
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
Early Exit
function findFirst<T>(arr: T[], pred: (x: T) => boolean): T | undefined {
for (const item of arr) {
if (pred(item)) return item;
}
return undefined;
}
Complex Mutations
function quickSort<T>(arr: T[]): T[] {
}
Converting Loops to Functional
const result: ProcessedItem[] = [];
for (const item of items) {
if (item.isValid) {
result.push(processItem(item));
}
}
const result = items
.filter(item => item.isValid)
.map(item => processItem(item));
Pressure Resistance Protocol
1. "Loops Are More Readable"
Pressure: "I understand for loops better"
Response: Functional methods express intent clearly: map = transform, filter = select, reduce = aggregate.
Action: Learn the patterns. They become natural quickly.
2. "Performance Concerns"
Pressure: "Multiple passes are slower"
Response: For most data sizes, clarity beats micro-optimization.
Action: Profile before optimizing. Most code isn't performance-critical.
Red Flags - STOP and Reconsider
const result = [] followed by loop pushing elements
- Type annotations needed only because of loops
- Complex state tracking in loops
any[] that should be more specific
Common Rationalizations (All Invalid)
| Excuse | Reality |
|---|
| "Loops are simpler" | Functional methods have clearer intent |
| "I need the index" | .map((item, i) => ...) provides index |
| "Multiple passes are slow" | Usually doesn't matter; measure first |
Quick Reference
const result: string[] = [];
for (const x of items) {
result.push(x.name);
}
const result = items.map(x => x.name);
const processed = items
.filter(x => x.isValid)
.map(x => transform(x));
const strings = mixed.filter((x): x is string => typeof x === 'string');
const grouped = items.reduce((acc, x) => ..., {} as GroupedType);
The Bottom Line
Functional constructs make types flow naturally.
map, filter, reduce, and similar methods produce well-typed results without manual annotation. They express transformations clearly and work excellently with TypeScript's inference. Use loops only when you have a specific reason to.
Reference
Based on "Effective TypeScript" by Dan Vanderkam, Item 26: Use Functional Constructs and Libraries to Help Types Flow.