javascript

exercises

exercises.js
/**
 * ============================================================
 * 22.5 PERFORMANCE OPTIMIZATION INTERNALS - EXERCISES
 * ============================================================
 *
 * Practice identifying and fixing performance anti-patterns,
 * understanding deoptimization, and writing engine-friendly code.
 */

/**
 * EXERCISE 1: IDENTIFY DEOPTIMIZATION CAUSES
 *
 * Analyze the following functions and identify what will cause
 * deoptimization. Then fix them.
 */

console.log('=== Exercise 1: Identify Deoptimization Causes ===\n');

// Problem 1: Type instability
function calculateValue(input) {
  if (typeof input === 'string') {
    return input.length;
  }
  if (typeof input === 'number') {
    return input * 2;
  }
  if (Array.isArray(input)) {
    return input.length;
  }
  return 0;
}

// TODO: Identify the problem and create type-specific functions
// calculateNumberValue, calculateStringValue, calculateArrayValue

// Problem 2: Shape inconsistency
function createUser(name, email, isAdmin) {
  const user = {};
  user.name = name;
  if (email) {
    user.email = email;
  }
  if (isAdmin) {
    user.role = 'admin';
    user.permissions = ['read', 'write', 'delete'];
  }
  return user;
}

// TODO: Fix to ensure all users have the same shape

// Problem 3: Arguments leaking
function logAllArguments() {
  console.log('Arguments:', arguments);
  return arguments; // Leaking!
}

// TODO: Fix using rest parameters

console.log('Fix the functions above, then verify:');
console.log('  calculateValue(5):', calculateValue(5));
console.log(
  "  createUser('Alice', 'a@b.com', true):",
  createUser('Alice', 'a@b.com', true)
);
console.log(
  "  createUser('Bob', null, false):",
  createUser('Bob', null, false)
);
console.log();

/**
 * EXERCISE 2: OPTIMIZE OBJECT CREATION
 *
 * Refactor the object creation patterns to be more engine-friendly.
 */

console.log('=== Exercise 2: Optimize Object Creation ===\n');

// Bad: Random property order
function createPointBad(x, y, z) {
  const point = {};
  if (z !== undefined) {
    point.z = z;
  }
  point.x = x;
  point.y = y;
  return point;
}

// TODO: Create createPointGood that always has same shape

// Bad: Adding properties after construction
class VectorBad {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  addZ(z) {
    this.z = z; // Changes shape!
    return this;
  }
}

// TODO: Create VectorGood that has consistent shape

// Test your solutions
console.log('Verify your solutions maintain consistent shapes');
console.log();

/**
 * EXERCISE 3: OPTIMIZE ARRAY OPERATIONS
 *
 * Fix the array operations to avoid common pitfalls.
 */

console.log('=== Exercise 3: Optimize Array Operations ===\n');

// Problem 1: Creating holey array
function createSequenceBad(n) {
  const arr = [];
  arr[n - 1] = n - 1; // Creates holey array!
  for (let i = 0; i < n - 1; i++) {
    arr[i] = i;
  }
  return arr;
}

// TODO: Create createSequenceGood that avoids holes

// Problem 2: Type mixing
function processNumbersBad(numbers) {
  const results = [];
  for (const n of numbers) {
    if (n < 0) {
      results.push('negative'); // String in number array!
    } else {
      results.push(n * 2);
    }
  }
  return results;
}

// TODO: Create processNumbersGood that keeps consistent types

// Problem 3: Delete in array
function removeMiddleBad(arr) {
  const mid = Math.floor(arr.length / 2);
  delete arr[mid]; // Creates hole!
  return arr;
}

// TODO: Create removeMiddleGood using splice

console.log('Test your array optimizations:');
console.log('  createSequenceBad(5):', createSequenceBad(5));
console.log('  processNumbersBad([1, -2, 3]):', processNumbersBad([1, -2, 3]));
console.log();

/**
 * EXERCISE 4: IMPLEMENT OBJECT POOL
 *
 * Create an object pool for particle system optimization.
 */

console.log('=== Exercise 4: Implement Object Pool ===\n');

class ParticlePool {
  constructor(size) {
    this.pool = [];
    this.activeCount = 0;

    // TODO: Pre-allocate particles with shape { x, y, vx, vy, life }
  }

  /**
   * Get a particle from the pool
   * @returns {Object} A particle object
   */
  acquire() {
    // TODO: Return a particle from pool, or create new if empty
    // Track activeCount
    return { x: 0, y: 0, vx: 0, vy: 0, life: 0 };
  }

  /**
   * Return a particle to the pool
   * @param {Object} particle - The particle to return
   */
  release(particle) {
    // TODO: Reset particle properties and return to pool
    // Update activeCount
  }

  /**
   * Get pool statistics
   */
  getStats() {
    return {
      poolSize: this.pool.length,
      active: this.activeCount,
      total: this.pool.length + this.activeCount,
    };
  }
}

// Test the pool
const pool = new ParticlePool(10);
console.log('Initial stats:', pool.getStats());

const particles = [];
for (let i = 0; i < 5; i++) {
  particles.push(pool.acquire());
}
console.log('After acquiring 5:', pool.getStats());

particles.forEach((p) => pool.release(p));
console.log('After releasing all:', pool.getStats());
console.log();

/**
 * EXERCISE 5: HOT/COLD CODE SEPARATION
 *
 * Refactor to separate hot and cold code paths.
 */

console.log('=== Exercise 5: Hot/Cold Code Separation ===\n');

// Bad: Cold code mixed with hot code
function processItemsBad(items) {
  const results = [];

  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    // Hot path
    const value = item.value * 2;

    // Cold path mixed in (validation, error handling)
    if (typeof item.value !== 'number') {
      console.error(`Invalid item at index ${i}:`, item);
      const error = new Error(`Expected number, got ${typeof item.value}`);
      error.item = item;
      error.index = i;
      // Maybe log to server, notify admin, etc.
      results.push({ error: true, index: i });
      continue;
    }

    if (value > 1000) {
      console.warn(`Large value at index ${i}:`, value);
      // Additional logging, metrics, etc.
    }

    results.push({ value, index: i });
  }

  return results;
}

// TODO: Create processItemsGood with hot/cold separation
// Move error handling and logging to separate functions

function processItemsGood(items) {
  // TODO: Implement with clean hot path
  return [];
}

// Helper functions for cold paths
// TODO: Create handleInvalidItem(item, index)
// TODO: Create handleLargeValue(value, index)

console.log('Test hot/cold separation:');
const testItems = [
  { value: 10 },
  { value: 'invalid' },
  { value: 600 },
  { value: 50 },
];
console.log('Bad version:', processItemsBad(testItems));
console.log();

/**
 * EXERCISE 6: MONOMORPHIC FUNCTION DESIGN
 *
 * Refactor polymorphic functions to be monomorphic.
 */

console.log('=== Exercise 6: Monomorphic Function Design ===\n');

// Bad: Polymorphic - handles many types
function getLength(thing) {
  if (typeof thing === 'string') {
    return thing.length;
  }
  if (Array.isArray(thing)) {
    return thing.length;
  }
  if (thing instanceof Map || thing instanceof Set) {
    return thing.size;
  }
  if (typeof thing === 'object' && thing !== null) {
    return Object.keys(thing).length;
  }
  return 0;
}

// TODO: Create type-specific monomorphic functions:
// getStringLength(str)
// getArrayLength(arr)
// getMapSize(map)
// getSetSize(set)
// getObjectKeyCount(obj)

console.log('Polymorphic (bad):');
console.log("  getLength('hello'):", getLength('hello'));
console.log('  getLength([1,2,3]):', getLength([1, 2, 3]));
console.log('  getLength({a:1,b:2}):', getLength({ a: 1, b: 2 }));

console.log('\nCreate monomorphic alternatives:');
console.log("  getStringLength('hello')");
console.log('  getArrayLength([1,2,3])');
console.log('  getObjectKeyCount({a:1,b:2})');
console.log();

/**
 * EXERCISE 7: STRING BUILDING OPTIMIZATION
 *
 * Optimize string concatenation for large outputs.
 */

console.log('=== Exercise 7: String Building Optimization ===\n');

// Bad: Repeated concatenation
function buildHTMLBad(items) {
  let html = '<ul>';
  for (const item of items) {
    html += '<li>';
    html += item.name;
    html += ': ';
    html += item.value;
    html += '</li>';
  }
  html += '</ul>';
  return html;
}

// TODO: Create buildHTMLGood using array.join()

// TODO: Create buildHTMLTemplate using template literals efficiently

const testData = [
  { name: 'Item 1', value: 100 },
  { name: 'Item 2', value: 200 },
  { name: 'Item 3', value: 300 },
];

console.log('Bad version:', buildHTMLBad(testData));
console.log('Implement good version using array.join()');
console.log();

/**
 * EXERCISE 8: INLINE CACHE OPTIMIZATION
 *
 * Fix code to maintain monomorphic inline caches.
 */

console.log('=== Exercise 8: Inline Cache Optimization ===\n');

// Bad: Different object shapes in same operation
function sumValuesBad(objects) {
  let sum = 0;
  for (const obj of objects) {
    sum += obj.value; // IC becomes polymorphic/megamorphic
  }
  return sum;
}

// Objects with different shapes
const badObjects = [
  { value: 1 },
  { value: 2, extra: 'data' },
  { type: 'special', value: 3 },
  { value: 4, nested: { data: true } },
];

// TODO: Create normalizeObjects(objects) that returns objects with same shape
// Then create sumValuesGood that works on normalized objects

console.log('Bad: Mixed shapes cause megamorphic IC');
console.log('  Sum:', sumValuesBad(badObjects));
console.log('\nFix by normalizing object shapes first');
console.log();

/**
 * EXERCISE 9: TYPED ARRAY OPERATIONS
 *
 * Use typed arrays for numeric operations.
 */

console.log('=== Exercise 9: Typed Array Operations ===\n');

// Bad: Regular array for numeric data
function processCoordinatesBad(coords) {
  // coords is [[x, y], [x, y], ...]
  const results = [];
  for (const [x, y] of coords) {
    const distance = Math.sqrt(x * x + y * y);
    results.push(distance);
  }
  return results;
}

// TODO: Create processCoordinatesGood using Float64Array
// Input: Float64Array of alternating x, y values
// Output: Float64Array of distances

// TODO: Create functions:
// coordsToTypedArray(coords) - converts [[x,y], ...] to Float64Array
// typedArrayToCoords(typedArr) - converts back

console.log('Regular array version:');
const coords = [
  [3, 4],
  [5, 12],
  [8, 15],
];
console.log('  Input:', coords);
console.log('  Distances:', processCoordinatesBad(coords));

console.log('\nImplement typed array version for better performance');
console.log();

/**
 * EXERCISE 10: PERFORMANCE BENCHMARKING
 *
 * Create a benchmarking utility to compare implementations.
 */

console.log('=== Exercise 10: Performance Benchmarking ===\n');

class Benchmark {
  /**
   * Run a function multiple times and measure performance
   * @param {Function} fn - Function to benchmark
   * @param {number} iterations - Number of iterations
   * @param {number} warmup - Warmup iterations
   * @returns {Object} - Timing statistics
   */
  static run(fn, iterations = 1000, warmup = 100) {
    // TODO: Implement benchmarking
    // 1. Run warmup iterations (don't measure)
    // 2. Run measured iterations
    // 3. Calculate min, max, average, total

    return {
      iterations,
      total: 0,
      average: 0,
      min: 0,
      max: 0,
    };
  }

  /**
   * Compare two functions
   * @param {string} name1 - Name of first function
   * @param {Function} fn1 - First function
   * @param {string} name2 - Name of second function
   * @param {Function} fn2 - Second function
   * @param {number} iterations - Number of iterations
   * @returns {Object} - Comparison results
   */
  static compare(name1, fn1, name2, fn2, iterations = 1000) {
    // TODO: Run both benchmarks and compare
    // Return which is faster and by how much

    return {
      winner: name1,
      ratio: 1,
    };
  }

  /**
   * Format benchmark results as string
   * @param {string} name - Benchmark name
   * @param {Object} results - Benchmark results
   * @returns {string} - Formatted string
   */
  static format(name, results) {
    // TODO: Create readable output
    return `${name}: ${results.total}ms`;
  }
}

// Test the benchmark
console.log('Benchmark utility created. Test with:');
console.log('  const result = Benchmark.run(() => arrayOperation());');
console.log("  console.log(Benchmark.format('Operation', result));");
console.log();

/**
 * ============================================================
 * SOLUTIONS
 * ============================================================
 */

console.log('=== SOLUTIONS ===\n');

// Solution 1: Type-specific functions
console.log('--- Solution 1: Type-Specific Functions ---\n');

function calculateNumberValue(input) {
  return input * 2;
}

function calculateStringValue(input) {
  return input.length;
}

function calculateArrayValue(input) {
  return input.length;
}

// Fixed user creation with consistent shape
function createUserFixed(name, email, isAdmin) {
  return {
    name: name,
    email: email ?? null,
    role: isAdmin ? 'admin' : 'user',
    permissions: isAdmin ? ['read', 'write', 'delete'] : ['read'],
  };
}

// Fixed arguments using rest params
function logAllArgumentsFixed(...args) {
  console.log('Arguments:', args);
  return args;
}

console.log('Type-specific functions:');
console.log('  calculateNumberValue(5):', calculateNumberValue(5));
console.log("  calculateStringValue('hello'):", calculateStringValue('hello'));

console.log('\nConsistent shape:');
console.log('  Admin:', createUserFixed('Alice', 'a@b.com', true));
console.log('  User:', createUserFixed('Bob', null, false));
console.log();

// Solution 2: Object creation
console.log('--- Solution 2: Optimized Object Creation ---\n');

function createPointGood(x, y, z) {
  return {
    x: x,
    y: y,
    z: z !== undefined ? z : null,
  };
}

class VectorGood {
  constructor(x, y, z = null) {
    this.x = x;
    this.y = y;
    this.z = z; // Always has z
  }

  setZ(z) {
    this.z = z;
    return this;
  }
}

console.log('createPointGood(1, 2):', createPointGood(1, 2));
console.log('createPointGood(1, 2, 3):', createPointGood(1, 2, 3));
console.log('new VectorGood(1, 2):', new VectorGood(1, 2));
console.log();

// Solution 3: Array optimization
console.log('--- Solution 3: Array Optimization ---\n');

function createSequenceGood(n) {
  const arr = new Array(n);
  for (let i = 0; i < n; i++) {
    arr[i] = i;
  }
  return arr;
}

function processNumbersGood(numbers) {
  const results = [];
  for (const n of numbers) {
    if (n < 0) {
      results.push(n * -2); // Keep as number
    } else {
      results.push(n * 2);
    }
  }
  return results;
}

function removeMiddleGood(arr) {
  const mid = Math.floor(arr.length / 2);
  arr.splice(mid, 1);
  return arr;
}

console.log('createSequenceGood(5):', createSequenceGood(5));
console.log('processNumbersGood([1, -2, 3]):', processNumbersGood([1, -2, 3]));
console.log(
  'removeMiddleGood([1,2,3,4,5]):',
  removeMiddleGood([1, 2, 3, 4, 5])
);
console.log();

// Solution 4: Object Pool
console.log('--- Solution 4: Object Pool ---\n');

class ParticlePoolSolution {
  constructor(size) {
    this.pool = [];
    this.activeCount = 0;

    for (let i = 0; i < size; i++) {
      this.pool.push({ x: 0, y: 0, vx: 0, vy: 0, life: 0 });
    }
  }

  acquire() {
    this.activeCount++;

    if (this.pool.length > 0) {
      return this.pool.pop();
    }

    return { x: 0, y: 0, vx: 0, vy: 0, life: 0 };
  }

  release(particle) {
    particle.x = 0;
    particle.y = 0;
    particle.vx = 0;
    particle.vy = 0;
    particle.life = 0;

    this.pool.push(particle);
    this.activeCount--;
  }

  getStats() {
    return {
      poolSize: this.pool.length,
      active: this.activeCount,
      total: this.pool.length + this.activeCount,
    };
  }
}

const poolSol = new ParticlePoolSolution(10);
console.log('Initial:', poolSol.getStats());
const p1 = poolSol.acquire();
const p2 = poolSol.acquire();
console.log('After 2 acquires:', poolSol.getStats());
poolSol.release(p1);
console.log('After 1 release:', poolSol.getStats());
console.log();

// Solution 5: Hot/Cold Separation
console.log('--- Solution 5: Hot/Cold Separation ---\n');

function handleInvalidItemSol(item, index) {
  console.error(`Invalid item at index ${index}:`, item);
  return { error: true, index };
}

function handleLargeValueSol(value, index) {
  console.warn(`Large value at index ${index}:`, value);
}

function processItemsGoodSol(items) {
  const results = [];

  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    if (typeof item.value !== 'number') {
      results.push(handleInvalidItemSol(item, i));
      continue;
    }

    const value = item.value * 2;

    if (value > 1000) {
      handleLargeValueSol(value, i);
    }

    results.push({ value, index: i });
  }

  return results;
}

console.log('Hot/cold separated version works the same');
console.log('but keeps hot path clean for optimization');
console.log();

// Solution 6: Monomorphic functions
console.log('--- Solution 6: Monomorphic Functions ---\n');

const getStringLength = (str) => str.length;
const getArrayLength = (arr) => arr.length;
const getMapSize = (map) => map.size;
const getSetSize = (set) => set.size;
const getObjectKeyCount = (obj) => Object.keys(obj).length;

console.log("getStringLength('hello'):", getStringLength('hello'));
console.log('getArrayLength([1,2,3]):', getArrayLength([1, 2, 3]));
console.log('getObjectKeyCount({a:1,b:2}):', getObjectKeyCount({ a: 1, b: 2 }));
console.log();

// Solution 7: String building
console.log('--- Solution 7: String Building ---\n');

function buildHTMLGood(items) {
  const parts = ['<ul>'];
  for (const item of items) {
    parts.push(`<li>${item.name}: ${item.value}</li>`);
  }
  parts.push('</ul>');
  return parts.join('');
}

console.log('buildHTMLGood:', buildHTMLGood(testData));
console.log();

// Solution 8: IC optimization
console.log('--- Solution 8: IC Optimization ---\n');

function normalizeObjects(objects) {
  return objects.map((obj) => ({
    value: obj.value,
    extra: null,
    type: null,
    nested: null,
  }));
}

function sumValuesGood(objects) {
  let sum = 0;
  for (const obj of objects) {
    sum += obj.value;
  }
  return sum;
}

const normalizedObjects = normalizeObjects(badObjects);
console.log('Normalized objects all have same shape');
console.log('Sum:', sumValuesGood(normalizedObjects));
console.log();

// Solution 9: Typed arrays
console.log('--- Solution 9: Typed Arrays ---\n');

function coordsToTypedArray(coords) {
  const arr = new Float64Array(coords.length * 2);
  for (let i = 0; i < coords.length; i++) {
    arr[i * 2] = coords[i][0];
    arr[i * 2 + 1] = coords[i][1];
  }
  return arr;
}

function processCoordinatesGood(typedCoords) {
  const count = typedCoords.length / 2;
  const distances = new Float64Array(count);

  for (let i = 0; i < count; i++) {
    const x = typedCoords[i * 2];
    const y = typedCoords[i * 2 + 1];
    distances[i] = Math.sqrt(x * x + y * y);
  }

  return distances;
}

const typedCoords = coordsToTypedArray(coords);
const distances = processCoordinatesGood(typedCoords);
console.log('Typed array input:', typedCoords);
console.log('Distances:', distances);
console.log();

// Solution 10: Benchmarking
console.log('--- Solution 10: Benchmarking ---\n');

class BenchmarkSolution {
  static run(fn, iterations = 1000, warmup = 100) {
    // Warmup
    for (let i = 0; i < warmup; i++) {
      fn();
    }

    const times = [];

    for (let i = 0; i < iterations; i++) {
      const start = performance.now();
      fn();
      const end = performance.now();
      times.push(end - start);
    }

    const total = times.reduce((a, b) => a + b, 0);

    return {
      iterations,
      total,
      average: total / iterations,
      min: Math.min(...times),
      max: Math.max(...times),
    };
  }

  static compare(name1, fn1, name2, fn2, iterations = 1000) {
    const result1 = this.run(fn1, iterations);
    const result2 = this.run(fn2, iterations);

    const winner = result1.average < result2.average ? name1 : name2;
    const ratio =
      Math.max(result1.average, result2.average) /
      Math.min(result1.average, result2.average);

    return {
      [name1]: result1,
      [name2]: result2,
      winner,
      ratio: ratio.toFixed(2),
    };
  }

  static format(name, results) {
    return (
      `${name}: total=${results.total.toFixed(2)}ms, ` +
      `avg=${results.average.toFixed(4)}ms, ` +
      `min=${results.min.toFixed(4)}ms, ` +
      `max=${results.max.toFixed(4)}ms`
    );
  }
}

const testFn = () => {
  let sum = 0;
  for (let i = 0; i < 100; i++) sum += i;
  return sum;
};

const benchResult = BenchmarkSolution.run(testFn, 100);
console.log(BenchmarkSolution.format('Test function', benchResult));
console.log();

console.log('=== Exercises Complete ===\n');
Exercises - JavaScript Tutorial | DeepML