# Performance Optimization Checklist - HTML Chess Game
## Overview
This checklist provides a comprehensive guide for implementing performance optimizations throughout the development lifecycle. Use this as a companion to the [Performance Budget](/docs/analysis/performance-budget.md) document.
---
## Pre-Implementation Optimizations
### ✅ Architecture Planning
- [ ] **Choose optimal data structures**
- [ ] Use typed arrays for board representation (faster than objects)
- [ ] Implement bitboards for advanced features (optional)
- [ ] Plan for immutable game state (easier undo/redo)
- [ ] Design for minimal object creation in hot paths
- [ ] **Plan component interfaces**
- [ ] Define clear boundaries between modules
- [ ] Minimize cross-module dependencies
- [ ] Design for lazy loading (AI module separate)
- [ ] Plan event system to reduce coupling
- [ ] **Optimize build pipeline**
- [ ] Set up code splitting (core vs AI vs sounds)
- [ ] Configure tree shaking (ES6 modules)
- [ ] Enable minification and compression
- [ ] Set up source maps for debugging
### ✅ Development Environment
- [ ] **Performance tooling setup**
- [ ] Install Chrome DevTools extensions
- [ ] Set up Lighthouse CI
- [ ] Configure bundle size analyzer
- [ ] Add performance test suite
- [ ] Set up memory profiling
- [ ] **Budgets configuration**
- [ ] Add webpack-bundle-analyzer
- [ ] Configure size-limit package
- [ ] Set up performance budgets in webpack
- [ ] Create pre-commit hooks for budget checks
### ✅ Code Quality Standards
- [ ] **Establish coding patterns**
- [ ] Use const/let (no var) for better optimization
- [ ] Prefer pure functions for testability
- [ ] Avoid premature abstraction
- [ ] Document performance-critical sections
- [ ] Use consistent naming conventions
- [ ] **Performance-aware coding**
- [ ] Avoid nested loops where possible
- [ ] Use early returns to reduce nesting
- [ ] Cache expensive calculations
- [ ] Minimize DOM access in loops
- [ ] Prefer CSS classes over inline styles
---
## During-Implementation Best Practices
### ⚡ JavaScript Performance
#### 1. General Optimizations
- [ ] **Variable usage**
- [ ] Use local variables over object properties
- [ ] Cache array/string lengths in loops
- [ ] Minimize global variable access
- [ ] Use destructuring sparingly (creates overhead)
```javascript
// ❌ Bad - Property access in loop
for (let i = 0; i < pieces.length; i++) {
if (pieces[i].color === this.currentColor) { ... }
}
// ✅ Good - Cached values
const pieceCount = pieces.length;
const currentColor = this.currentColor;
for (let i = 0; i < pieceCount; i++) {
if (pieces[i].color === currentColor) { ... }
}
```
- [ ] **Function calls**
- [ ] Minimize function call overhead in loops
- [ ] Inline trivial functions (< 3 lines)
- [ ] Use arrow functions for callbacks
- [ ] Avoid creating functions inside loops
```javascript
// ❌ Bad - Function creation in loop
moves.forEach(function(move) {
validateMove(move);
});
// ✅ Good - Predefined function
moves.forEach(validateMove);
```
- [ ] **Object operations**
- [ ] Use Object.create(null) for dictionaries
- [ ] Prefer Map for key-value pairs with many operations
- [ ] Use WeakMap for memory-sensitive caches
- [ ] Avoid delete operator (use null instead)
```javascript
// ❌ Bad - delete causes hidden class change
delete obj.property;
// ✅ Good - null maintains hidden class
obj.property = null;
```
#### 2. Array Operations
- [ ] **Efficient array methods**
- [ ] Use for loops for performance-critical paths
- [ ] Prefer forEach/map/filter for readability (non-critical)
- [ ] Use Array.from() sparingly (creates new array)
- [ ] Preallocate arrays when size is known
```javascript
// ❌ Bad - Dynamic growth
const moves = [];
for (let i = 0; i < 64; i++) {
moves.push(generateMove(i));
}
// ✅ Good - Preallocated
const moves = new Array(64);
for (let i = 0; i < 64; i++) {
moves[i] = generateMove(i);
}
```
- [ ] **Array searching**
- [ ] Use indexOf/includes for small arrays
- [ ] Use Set for large arrays (O(1) lookup)
- [ ] Break early from loops when found
- [ ] Consider binary search for sorted arrays
#### 3. String Operations
- [ ] **String concatenation**
- [ ] Use template literals for readability
- [ ] Use array.join() for multiple concatenations
- [ ] Avoid + in loops
- [ ] Use String.prototype methods efficiently
```javascript
// ❌ Bad - Multiple concatenations
let notation = '';
notation += piece;
notation += fromSquare;
notation += toSquare;
// ✅ Good - Template literal
const notation = `${piece}${fromSquare}${toSquare}`;
```
### ⚡ AI Performance
#### 4. Search Algorithm Optimizations
- [ ] **Alpha-Beta Pruning** (CRITICAL - 10-100x improvement)
- [ ] Implement fail-soft alpha-beta
- [ ] Use proper window management
- [ ] Track pruning statistics
- [ ] Test with various positions
```javascript
// ✅ Alpha-Beta Implementation
function alphaBeta(depth, alpha, beta, maximizing, gameState) {
if (depth === 0 || gameState.isTerminal()) {
return evaluate(gameState);
}
const moves = generateMoves(gameState);
if (maximizing) {
let maxEval = -Infinity;
for (const move of moves) {
const newState = makeMove(gameState, move);
const eval = alphaBeta(depth - 1, alpha, beta, false, newState);
maxEval = Math.max(maxEval, eval);
alpha = Math.max(alpha, eval);
if (beta <= alpha) break; // Beta cutoff
}
return maxEval;
} else {
// Similar for minimizing
}
}
```
- [ ] **Move Ordering** (HIGH - 2-3x improvement on top of alpha-beta)
- [ ] Evaluate captures first (MVV/LVA)
- [ ] Try check-giving moves early
- [ ] Use killer move heuristic
- [ ] Use hash move from transposition table
```javascript
// ✅ Move Ordering
function orderMoves(moves, gameState) {
return moves.sort((a, b) => {
// 1. Hash move first
if (a === hashMove) return -1;
if (b === hashMove) return 1;
// 2. Captures (MVV/LVA - Most Valuable Victim, Least Valuable Attacker)
const aScore = getCaptureScore(a);
const bScore = getCaptureScore(b);
if (aScore !== bScore) return bScore - aScore;
// 3. Killer moves
if (isKillerMove(a)) return -1;
if (isKillerMove(b)) return 1;
// 4. History heuristic
return getHistoryScore(b) - getHistoryScore(a);
});
}
```
- [ ] **Transposition Table** (HIGH - 1.5-2x improvement)
- [ ] Use Zobrist hashing for positions
- [ ] Implement replacement strategy (depth-preferred)
- [ ] Store bounds (exact, lower, upper)
- [ ] Size table based on device memory
```javascript
// ✅ Transposition Table
class TranspositionTable {
constructor(maxSize = 10_000_000) {
this.table = new Map();
this.maxSize = maxSize;
}
store(hash, depth, score, flag, bestMove) {
// Replacement strategy: prefer deeper searches
const existing = this.table.get(hash);
if (!existing || depth >= existing.depth) {
this.table.set(hash, { depth, score, flag, bestMove });
}
// Evict oldest entries if too large
if (this.table.size > this.maxSize) {
const firstKey = this.table.keys().next().value;
this.table.delete(firstKey);
}
}
probe(hash, depth, alpha, beta) {
const entry = this.table.get(hash);
if (!entry || entry.depth < depth) return null;
// Check if we can use this score
if (entry.flag === EXACT) return entry.score;
if (entry.flag === LOWER && entry.score >= beta) return entry.score;
if (entry.flag === UPPER && entry.score <= alpha) return entry.score;
return null;
}
}
```
- [ ] **Iterative Deepening** (MEDIUM - Better UX)
- [ ] Start at depth 1, increment each iteration
- [ ] Use previous iteration for move ordering
- [ ] Support time-based termination
- [ ] Return best move from completed depth
```javascript
// ✅ Iterative Deepening
function iterativeDeepening(gameState, maxTime = 2000) {
const startTime = performance.now();
let bestMove = null;
let depth = 1;
while (performance.now() - startTime < maxTime) {
try {
const result = alphaBeta(depth, -Infinity, Infinity, true, gameState);
bestMove = result.move;
depth++;
} catch (e) {
if (e instanceof TimeoutError) break;
throw e;
}
}
return bestMove;
}
```
#### 5. Evaluation Function Optimizations
- [ ] **Incremental Updates** (HIGH - 5x improvement)
- [ ] Track material score incrementally
- [ ] Update positional scores on move/unmove
- [ ] Only recalculate complex metrics when needed
- [ ] Cache king safety calculations
```javascript
// ✅ Incremental Evaluation
class IncrementalEvaluator {
constructor() {
this.materialScore = 0;
this.positionalScore = 0;
}
makeMove(move) {
// Update material
if (move.captured) {
this.materialScore -= PIECE_VALUES[move.captured];
}
// Update positional (only changed squares)
this.positionalScore -= PIECE_SQUARE_TABLES[move.piece][move.from];
this.positionalScore += PIECE_SQUARE_TABLES[move.piece][move.to];
}
unmakeMove(move) {
// Reverse updates
if (move.captured) {
this.materialScore += PIECE_VALUES[move.captured];
}
this.positionalScore += PIECE_SQUARE_TABLES[move.piece][move.from];
this.positionalScore -= PIECE_SQUARE_TABLES[move.piece][move.to];
}
getScore() {
return this.materialScore + this.positionalScore;
}
}
```
- [ ] **Piece-Square Tables** (MEDIUM - 2x improvement)
- [ ] Precompute all positional values
- [ ] Use O(1) array lookups
- [ ] Separate tables for opening/middlegame/endgame
- [ ] Mirror tables for black pieces
```javascript
// ✅ Piece-Square Tables
const PAWN_TABLE = [
[ 0, 0, 0, 0, 0, 0, 0, 0],
[ 50, 50, 50, 50, 50, 50, 50, 50],
[ 10, 10, 20, 30, 30, 20, 10, 10],
[ 5, 5, 10, 25, 25, 10, 5, 5],
[ 0, 0, 0, 20, 20, 0, 0, 0],
[ 5, -5,-10, 0, 0,-10, -5, 5],
[ 5, 10, 10,-20,-20, 10, 10, 5],
[ 0, 0, 0, 0, 0, 0, 0, 0]
];
function getPieceSquareScore(piece, square, color) {
const [rank, file] = squareToCoords(square);
const tableRank = color === 'white' ? rank : 7 - rank;
return PAWN_TABLE[tableRank][file];
}
```
#### 6. Memory Optimizations
- [ ] **Object Pooling** (MEDIUM - 20-30% less GC)
- [ ] Pool move objects
- [ ] Pool position objects
- [ ] Reuse evaluation contexts
- [ ] Monitor pool size
```javascript
// ✅ Object Pool
class ObjectPool {
constructor(factory, initialSize = 100) {
this.factory = factory;
this.pool = [];
for (let i = 0; i < initialSize; i++) {
this.pool.push(factory());
}
}
acquire() {
return this.pool.length > 0 ? this.pool.pop() : this.factory();
}
release(obj) {
obj.reset(); // Clear object state
this.pool.push(obj);
}
}
// Usage
const movePool = new ObjectPool(() => new Move(), 1000);
const move = movePool.acquire();
// ... use move ...
movePool.release(move);
```
- [ ] **Garbage Collection Minimization**
- [ ] Avoid creating objects in hot loops
- [ ] Reuse arrays instead of creating new ones
- [ ] Use primitive values where possible
- [ ] Clear references when done
### ⚡ DOM Performance
#### 7. Rendering Optimizations
- [ ] **Virtual DOM / Diffing** (CRITICAL - 5-10x improvement)
- [ ] Track previous board state
- [ ] Only update changed squares
- [ ] Batch DOM updates
- [ ] Use DocumentFragment for batch inserts
```javascript
// ✅ DOM Diffing
function updateBoard(oldState, newState) {
const changedSquares = [];
for (let i = 0; i < 64; i++) {
if (oldState[i] !== newState[i]) {
changedSquares.push(i);
}
}
// Only update changed squares
requestAnimationFrame(() => {
changedSquares.forEach(index => {
updateSquare(index, newState[index]);
});
});
}
```
- [ ] **CSS Transform Animations** (CRITICAL - GPU acceleration)
- [ ] Use transform instead of top/left
- [ ] Use translate3d for GPU acceleration
- [ ] Avoid animating layout properties
- [ ] Use will-change sparingly
```css
/* ❌ Bad - Triggers layout */
.piece {
transition: top 0.3s, left 0.3s;
}
/* ✅ Good - GPU accelerated */
.piece {
transition: transform 0.3s;
will-change: transform;
}
```
```javascript
// ✅ Transform Animation
function animatePiece(piece, fromSquare, toSquare) {
const fromPos = getSquarePosition(fromSquare);
const toPos = getSquarePosition(toSquare);
const deltaX = toPos.x - fromPos.x;
const deltaY = toPos.y - fromPos.y;
piece.style.transform = `translate3d(${deltaX}px, ${deltaY}px, 0)`;
}
```
- [ ] **CSS Classes over Inline Styles** (MEDIUM - 2x improvement)
- [ ] Define CSS classes for all states
- [ ] Use classList API for toggling
- [ ] Avoid style.property = value
- [ ] Batch class changes
```javascript
// ❌ Bad - Inline styles
square.style.backgroundColor = '#f0d9b5';
square.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
// ✅ Good - CSS classes
square.classList.add('highlighted');
```
- [ ] **RequestAnimationFrame** (MEDIUM - Smooth animations)
- [ ] Use RAF for all animations
- [ ] Batch reads and writes
- [ ] Avoid layout thrashing
- [ ] Cancel RAF on cleanup
```javascript
// ✅ RequestAnimationFrame
function smoothUpdate() {
// Read phase (all DOM reads together)
const positions = pieces.map(p => p.getBoundingClientRect());
requestAnimationFrame(() => {
// Write phase (all DOM writes together)
positions.forEach((pos, i) => {
pieces[i].style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;
});
});
}
```
#### 8. Event Handling
- [ ] **Event Delegation** (MEDIUM - Reduces listeners)
- [ ] Use single listener on board container
- [ ] Identify target square from event.target
- [ ] Avoid listeners on every square
- [ ] Clean up listeners on destroy
```javascript
// ❌ Bad - 64 event listeners
squares.forEach(square => {
square.addEventListener('click', handleSquareClick);
});
// ✅ Good - 1 event listener
board.addEventListener('click', (event) => {
const square = event.target.closest('.square');
if (square) handleSquareClick(square);
});
```
- [ ] **Debouncing and Throttling**
- [ ] Debounce window resize handlers
- [ ] Throttle scroll handlers
- [ ] Use passive listeners for scroll/touch
- [ ] Remove listeners when not needed
```javascript
// ✅ Passive Listeners
board.addEventListener('touchstart', handleTouch, { passive: true });
// ✅ Throttle
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}
window.addEventListener('resize', throttle(handleResize, 200));
```
### ⚡ Asset Optimization
#### 9. Image Optimization
- [ ] **SVG Sprites** (MEDIUM - 50% size reduction)
- [ ] Combine all pieces into single SVG
- [ ] Use