Here are some of the rules, which are organized into four categories: data structures, logic, loops, functions.
They are originally from the book Programming Pearls by Jon Bentley. We will be applying them to JS/TS.
Data Structures Rules
- Packing and Encoding
verbose object with full property names
const user = { isActive: true, accountType: 'premium' };
better: packed encoding using bit flags
enum UserFlags {
Active = 1 << 0,
Premium = 1 << 1,
}
const userFlags = UserFlags.Active | UserFlags.Premium;
- Augmentation
O(n) lookup in array for common operations
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const getUser = (id) => users.find(u => u.id === id);
better: augment with Map for O(1) lookups
const userMap = new Map(users.map(u => [u.id, u]));
const getUserFast = (id) => userMap.get(id);
- Precomputation & Caching
recalculating expensive data on each render (react)
function ExpensiveComponent({ data }) {
const processed = heavyComputation(data);
return <div>{processed}</div>;
}
better: precompute with useMemo
function ExpensiveComponent({ data }) {
const processed = useMemo(() => heavyComputation(data), [data]);
return <div>{processed}</div>;
}
- Sparsity
dense array for sparse data (wasted memory/iteration)
const matrix = Array(10000).fill(0);
matrix[0] = 1;
matrix[9999] = 1;
better: sparse representation using Map
const sparseMatrix = new Map();
sparseMatrix.set(0, 1);
sparseMatrix.set(9999, 1);
Logic Rules
- Short-Circuiting & Test Ordering
expensive call every time
if (user && user.address && validateAddressWithAPI(user.address)) { }
better: cheap checks first, expensive API call last
if (user?.address && await validateAddressWithAPI(user.address)) { }
- Creating a Fast Path
fast path for common case in (nest.js)
@Injectable()
export class AuthGuard {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
//fast path: public routes skip all auth logic
if (this.isPublicRoute(request)) return true;
//slow path: full authentication check
return this.authenticate(request);
}
}
Loops Rules
- Hoisting
array length accessed each iteration
for (let i = 0; i < array.length; i++) { }
better: length hoisted
const len = array.length;
for (let i = 0; i < len; i++) { }
also modern for-of hoists internally
for (const item of array) { }
- Loop Fusion (react/next.js)
two separate loops over same data
users.forEach(user => updateCache(user));
users.forEach(user => notifySubscribers(user));
better: single loop
users.forEach(user => {
updateCache(user);
notifySubscribers(user);
});
Functions Rules
- Inlining
tiny function with overhead
const isAdult = (age) => age >= 18;
if (isAdult(user.age)) { }
better: inline simple logic (V8 may inline automatically though, but helps readability)
if (user.age >= 18) { }
- Coarsening Recursion
recursion for every small chunk
function processChunk(data, index) {
if (index >= data.length) return;
doWork(data[index]);
processChunk(data, index + 1);
}
better: coarsened: process in batches to reduce call overhead
function processBatch(data, start) {
const end = Math.min(start + 100, data.length);
for (let i = start; i < end; i++) doWork(data[i]);
if (end < data.length) processBatch(data, end);
}
Framework-Specific Notes
- react: useMemo, useCallback, React.memo implement caching and precomputation. Component tree structure affects pipeline/parallelism via concurrent rendering
- next.js: static generation (SSG) is compile-time precomputation. Middleware allows request fast-paths. Caching headers implement broader caching rules
- nest.js: guards and interceptors enable test ordering and fast-path patterns. Dependency injection supports augmentation (caching instances)