r;
category: 'error' | 'warning' | 'info';
payload: Record<string, unknown>;
acknowledged: boolean;
}
### 2. Transform Without Mutation
Use `map()` when you need a 1:1 transformation. Never mutate the source array inside the callback.
```typescript
const rawEvents: TelemetryEvent[] = [
{ id: 'evt-01', timestamp: 1715000000, category: 'error', payload: { code: 500 }, acknowledged: false },
{ id: 'evt-02', timestamp: 1715000100, category: 'info', payload: { code: 200 }, acknowledged: true },
{ id: 'evt-03', timestamp: 1715000200, category: 'warning', payload: { code: 429 }, acknowledged: false },
];
// Extract and normalize timestamps
const normalizedTimestamps = rawEvents.map(event => ({
eventId: event.id,
isoTime: new Date(event.timestamp).toISOString(),
}));
3. Filter with Intent
filter() creates a new array containing only elements that satisfy the predicate. It does not short-circuit; it evaluates every element.
const unacknowledgedErrors = rawEvents.filter(
event => event.category === 'error' && !event.acknowledged
);
4. Short-Circuit When Possible
If you only need to verify existence or locate the first match, use some(), every(), find(), or findIndex(). These methods stop iterating as soon as the condition is met or failed, saving CPU cycles on large collections.
const hasCriticalFailure = rawEvents.some(event => event.payload.code === 503);
const firstWarningIndex = rawEvents.findIndex(event => event.category === 'warning');
5. Aggregate with Precision
reduce() is powerful but often misused. Reserve it for true accumulation (sums, grouping, building maps). Provide an explicit initial value to avoid type inference errors.
const categoryCounts = rawEvents.reduce(
(acc, event) => {
acc[event.category] = (acc[event.category] ?? 0) + 1;
return acc;
},
{} as Record<string, number>
);
6. Sort Safely
sort() mutates the original array. In production systems, this causes unexpected state leaks. Use ES2023's toSorted() for immutable sorting, or explicitly copy before sorting.
// Immutable sort by timestamp (newest first)
const sortedEvents = rawEvents.toSorted((a, b) => b.timestamp - a.timestamp);
// Fallback for older environments
const sortedEventsLegacy = [...rawEvents].sort((a, b) => b.timestamp - a.timestamp);
7. Flatten and Map in One Pass
flatMap() applies a mapping function and flattens the result by one level. It is ideal for parsing delimited strings or extracting nested arrays without intermediate allocations.
const logLines = ['GET /api/v1', 'POST /api/v1/users', 'DELETE /api/v1/cache'];
const endpoints = logLines.flatMap(line => line.split(' '));
// ['GET', '/api/v1', 'POST', '/api/v1/users', 'DELETE', '/api/v1/cache']
slice() returns a shallow copy of a portion. splice() mutates the array and returns removed elements. Use slice() for read-only operations and splice() only when intentional mutation is required (e.g., queue processing).
const recentBatch = rawEvents.slice(0, 5); // Non-destructive
const processed = rawEvents.splice(0, 5); // Destructive, mutates rawEvents
Architecture Rationale
- Immutability by default: Prevents cross-component state corruption in React/Vue/Angular and simplifies debugging.
- Method selection by intent:
map for transformation, filter for selection, reduce for accumulation, find/some for early exit.
- Engine optimization: V8 recognizes built-in methods and applies hidden class caching, inline caching, and loop unrolling. Hand-rolled loops often bypass these optimizations.
- Type safety: Explicit return types and initial values prevent
any leakage and catch shape mismatches at compile time.
Pitfall Guide
1. Silent Mutation with sort() and splice()
Explanation: Both methods modify the source array in place. When passed by reference to multiple functions, this causes unpredictable state changes across the application.
Fix: Always use toSorted() (ES2023) or spread syntax [...arr].sort() for sorting. Replace splice() with slice() + filter() or immutable update patterns unless you explicitly manage a mutable queue.
2. The reduce() Overuse Trap
Explanation: Developers frequently use reduce() to replicate map() or filter() behavior, resulting in harder-to-read code and unnecessary accumulator overhead.
Fix: Reserve reduce() for true aggregation (sums, grouping, building dictionaries). Use map() for 1:1 transforms and filter() for selection. If a reduce() callback exceeds 5 lines, refactor into composed methods.
3. Ignoring Short-Circuit Behavior
Explanation: some(), every(), find(), and findIndex() stop iterating once the condition is satisfied or failed. Developers sometimes chain them after expensive transformations, negating the performance benefit.
Fix: Place short-circuit methods at the beginning of a pipeline or use them as guards before heavy computation. Example: if (!events.some(e => e.category === 'error')) return;
Explanation: Each method in a chain creates an intermediate array. Long chains (filter().map().filter().sort()) allocate multiple arrays in memory, causing GC pressure on large datasets.
Fix: Combine operations where possible (flatMap() for filter+map), use toSorted() only when necessary, or switch to a single reduce() pass for complex transformations on datasets >10k items.
5. flatMap() Depth Limitation Misunderstanding
Explanation: flatMap() only flattens one level deep. Nested arrays beyond depth 1 remain nested, leading to unexpected data shapes.
Fix: Use flat(Infinity) for recursive flattening, or explicitly map and flatten in stages. Never assume flatMap() handles arbitrary nesting.
6. slice() vs splice() Parameter Confusion
Explanation: slice(start, end) extracts without mutation. splice(start, deleteCount, ...items) mutates and returns removed elements. Mixing them up causes silent data loss or unexpected array modifications.
Fix: Memorize the signature: slice = read-only extraction, splice = write operation. Use TypeScript strict mode to catch type mismatches early.
7. Falsy Value Filtering Pitfalls
Explanation: filter(Boolean) removes all falsy values (0, '', false, null, undefined). This is dangerous when 0 or false are valid business values.
Fix: Use explicit predicates: filter(val => val !== null && val !== undefined) or filter(val => val != null). Reserve filter(Boolean) for cleanup tasks where falsy values are genuinely unwanted.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Transform every element 1:1 | .map() | Engine-optimized, predictable output, zero mutation | Low (single allocation) |
| Select subset based on condition | .filter() | Declarative, readable, maintains source integrity | Low (single allocation) |
| Verify existence or first match | .some() / .find() | Short-circuits, avoids full iteration | Minimal (early exit) |
| Aggregate into object/number | .reduce() | Flexible accumulator, but requires careful typing | Medium (depends on callback complexity) |
| Sort without side effects | .toSorted() (ES2023) | Immutable, prevents state leaks | Low (single allocation) |
| Large dataset (>50k items) | Single .reduce() or Web Worker | Reduces intermediate allocations, offloads main thread | High (initial setup) / Low (runtime) |
Configuration Template
// pipeline.ts
export type TransformFn<T, U> = (item: T) => U;
export type PredicateFn<T> = (item: T) => boolean;
export class DataPipeline<T> {
private source: readonly T[];
constructor(data: readonly T[]) {
this.source = data;
}
filter(predicate: PredicateFn<T>): DataPipeline<T> {
return new DataPipeline(this.source.filter(predicate));
}
map<U>(transform: TransformFn<T, U>): DataPipeline<U> {
return new DataPipeline(this.source.map(transform));
}
sort(comparator: (a: T, b: T) => number): DataPipeline<T> {
return new DataPipeline(this.source.toSorted(comparator));
}
find(predicate: PredicateFn<T>): T | undefined {
return this.source.find(predicate);
}
some(predicate: PredicateFn<T>): boolean {
return this.source.some(predicate);
}
reduce<U>(reducer: (acc: U, current: T) => U, initial: U): U {
return this.source.reduce(reducer, initial);
}
toArray(): readonly T[] {
return [...this.source];
}
}
// Usage
const events = [
{ id: '1', status: 'active', priority: 2 },
{ id: '2', status: 'inactive', priority: 1 },
{ id: '3', status: 'active', priority: 3 },
];
const result = new DataPipeline(events)
.filter(e => e.status === 'active')
.sort((a, b) => b.priority - a.priority)
.map(e => e.id)
.toArray();
Quick Start Guide
- Install TypeScript strict mode: Ensure
strict: true in tsconfig.json to catch implicit any types in array callbacks.
- Replace one loop: Identify a
for loop in your codebase and rewrite it using .map() or .filter(). Verify output matches.
- Add immutability guard: Replace
arr.sort() with arr.toSorted() or [...arr].sort(). Run tests to confirm no state leaks.
- Profile a pipeline: Use
console.time() or browser performance tab to measure execution time before and after refactoring. Note memory allocation changes.
- Adopt the template: Copy the
DataPipeline class into your utilities folder. Use it for complex transformations to enforce type safety and consistent chaining behavior.