javascript

exercises

exercises.js
/**
 * ============================================================
 * 22.3 ADVANCED GARBAGE COLLECTION - EXERCISES
 * ============================================================
 */

/**
 * EXERCISE 1: IDENTIFY MEMORY LEAKS
 *
 * Find the memory leaks in this code and explain why they occur.
 */

// Code to analyze:
class DataProcessor {
  constructor() {
    this.cache = {};
    this.handlers = [];
  }

  processData(key, data) {
    // Leak 1?
    this.cache[key] = this.transform(data);
    return this.cache[key];
  }

  transform(data) {
    const result = [];
    for (let i = 0; i < data.length; i++) {
      result.push(data[i] * 2);
    }
    return result;
  }

  addHandler(callback) {
    // Leak 2?
    this.handlers.push(callback);
  }

  processWithCallback(data, callback) {
    // Leak 3?
    const result = this.transform(data);

    setTimeout(() => {
      callback(result);
    }, 1000);

    return function cleanup() {
      // Does this help?
    };
  }
}

// YOUR ANALYSIS:
// Leak 1:
// Leak 2:
// Leak 3:

/**
 * EXERCISE 2: FIX THE MEMORY LEAKS
 *
 * Rewrite the DataProcessor class to fix all memory leaks.
 */

class DataProcessorFixed {
  constructor(options = {}) {
    // TODO: Initialize with proper cleanup mechanisms
  }

  processData(key, data) {
    // TODO: Implement with bounded cache
  }

  transform(data) {
    // TODO: Implement efficiently
  }

  addHandler(callback) {
    // TODO: Implement with cleanup support
  }

  removeHandler(callback) {
    // TODO: Implement handler removal
  }

  processWithCallback(data, callback) {
    // TODO: Implement with proper cleanup
  }

  destroy() {
    // TODO: Clean up all resources
  }
}

/**
 * EXERCISE 3: IMPLEMENT AN LRU CACHE
 *
 * Create an LRU (Least Recently Used) cache that automatically
 * evicts old entries to prevent memory leaks.
 */

class LRUCache {
  constructor(maxSize) {
    // TODO: Initialize data structures
  }

  get(key) {
    // TODO: Get value and update access order
    // Return undefined if not found
  }

  set(key, value) {
    // TODO: Set value, evict oldest if needed
  }

  has(key) {
    // TODO: Check if key exists
  }

  delete(key) {
    // TODO: Remove key
  }

  clear() {
    // TODO: Remove all entries
  }

  get size() {
    // TODO: Return current size
  }
}

// Test your implementation:
// const cache = new LRUCache(3);
// cache.set('a', 1);
// cache.set('b', 2);
// cache.set('c', 3);
// cache.get('a');      // Returns 1, 'a' is now most recent
// cache.set('d', 4);   // Should evict 'b' (least recently used)
// console.log(cache.has('b')); // Should be false
// console.log(cache.has('a')); // Should be true

/**
 * EXERCISE 4: IMPLEMENT AN OBJECT POOL
 *
 * Create a reusable object pool to reduce GC pressure.
 */

class ObjectPool {
  constructor(createFn, resetFn, initialSize = 10) {
    // TODO: Initialize pool
    // createFn: function to create new objects
    // resetFn: function to reset objects for reuse
  }

  acquire() {
    // TODO: Get object from pool or create new one
  }

  release(obj) {
    // TODO: Reset and return object to pool
  }

  drain() {
    // TODO: Empty the pool
  }

  getStats() {
    // TODO: Return statistics about pool usage
    // { created, acquired, released, poolSize, activeObjects }
  }
}

// Test your implementation:
// const pool = new ObjectPool(
//   () => ({ x: 0, y: 0 }),
//   (obj) => { obj.x = 0; obj.y = 0; }
// );
//
// const obj1 = pool.acquire();
// obj1.x = 10;
// pool.release(obj1);
//
// const obj2 = pool.acquire();
// console.log(obj2.x); // Should be 0 (reset)

/**
 * EXERCISE 5: WEAKMAP METADATA STORE
 *
 * Create a metadata store using WeakMap that allows objects
 * to be garbage collected.
 */

class ObjectMetadata {
  constructor() {
    // TODO: Initialize WeakMap-based storage
  }

  setMeta(obj, key, value) {
    // TODO: Set metadata for object
  }

  getMeta(obj, key) {
    // TODO: Get metadata for object
  }

  hasMeta(obj, key) {
    // TODO: Check if object has metadata key
  }

  deleteMeta(obj, key) {
    // TODO: Delete specific metadata
  }

  clearMeta(obj) {
    // TODO: Clear all metadata for object
  }
}

// Test:
// const meta = new ObjectMetadata();
// const user = { name: 'Alice' };
//
// meta.setMeta(user, 'createdAt', Date.now());
// meta.setMeta(user, 'visitCount', 1);
//
// console.log(meta.getMeta(user, 'visitCount')); // 1
//
// // When user is no longer referenced, metadata is also cleaned up

/**
 * EXERCISE 6: CIRCULAR REFERENCE CLEANUP
 *
 * Create a function that breaks circular references for
 * serialization purposes.
 */

function breakCircularReferences(obj, seen = new WeakSet()) {
  // TODO: Return a copy of obj with circular references replaced
  // by a placeholder like { $ref: 'circular' }
}

// Test:
// const a = { name: 'a' };
// const b = { name: 'b' };
// a.ref = b;
// b.ref = a;
//
// const cleaned = breakCircularReferences(a);
// console.log(JSON.stringify(cleaned, null, 2));
// Should work without circular reference error

/**
 * EXERCISE 7: EVENT EMITTER WITH AUTO-CLEANUP
 *
 * Create an event emitter that automatically removes listeners
 * when the subscribed objects are garbage collected.
 */

class SmartEventEmitter {
  constructor() {
    // TODO: Initialize with WeakRef-based listener storage
  }

  on(event, handler, context = null) {
    // TODO: Add listener, weakly referencing context if provided
    // Return unsubscribe function
  }

  emit(event, ...args) {
    // TODO: Emit event, cleaning up dead references
  }

  listenerCount(event) {
    // TODO: Return count of active listeners
  }

  cleanup() {
    // TODO: Remove all dead references
  }
}

/**
 * EXERCISE 8: MEMORY-EFFICIENT BUFFER
 *
 * Create a circular buffer that reuses memory.
 */

class CircularBuffer {
  constructor(capacity) {
    // TODO: Initialize buffer with fixed capacity
  }

  push(item) {
    // TODO: Add item, overwrite oldest if full
  }

  toArray() {
    // TODO: Return items in order (oldest to newest)
  }

  get length() {
    // TODO: Return number of items
  }

  get capacity() {
    // TODO: Return maximum capacity
  }

  get isFull() {
    // TODO: Return true if buffer is at capacity
  }

  clear() {
    // TODO: Clear buffer
  }
}

// Test:
// const buffer = new CircularBuffer(3);
// buffer.push(1);
// buffer.push(2);
// buffer.push(3);
// console.log(buffer.toArray()); // [1, 2, 3]
// buffer.push(4);
// console.log(buffer.toArray()); // [2, 3, 4] - 1 was overwritten

/**
 * EXERCISE 9: IDENTIFY GC-UNFRIENDLY PATTERNS
 *
 * Analyze this code and identify patterns that create GC pressure.
 */

function processDataBad(data) {
  // Pattern 1: Creating objects in loop
  const results = [];
  for (let i = 0; i < data.length; i++) {
    results.push({
      original: data[i],
      doubled: data[i] * 2,
      squared: data[i] ** 2,
      formatted: `Value: ${data[i]}`,
    });
  }

  // Pattern 2: String concatenation in loop
  let report = '';
  for (let i = 0; i < results.length; i++) {
    report += `Item ${i}: ${results[i].formatted}\n`;
  }

  // Pattern 3: Intermediate arrays
  const doubled = data.map((x) => x * 2);
  const filtered = doubled.filter((x) => x > 10);
  const sorted = filtered.sort((a, b) => a - b);
  const sliced = sorted.slice(0, 10);

  return { results, report, topTen: sliced };
}

// YOUR ANALYSIS:
// Pattern 1 issue:
// Pattern 2 issue:
// Pattern 3 issue:

// YOUR OPTIMIZED VERSION:
function processDataGood(data) {
  // TODO: Rewrite to minimize GC pressure
}

/**
 * EXERCISE 10: MEMORY MONITORING
 *
 * Create a memory monitor that tracks allocations over time.
 */

class MemoryMonitor {
  constructor() {
    // TODO: Initialize monitoring state
  }

  startTracking() {
    // TODO: Start periodic memory sampling
  }

  stopTracking() {
    // TODO: Stop tracking
  }

  getSamples() {
    // TODO: Return collected samples
  }

  getStats() {
    // TODO: Return statistics
    // { min, max, average, trend }
  }

  detectLeak(threshold) {
    // TODO: Return true if memory grew by more than threshold
  }
}

/**
 * EXERCISE 11: LAZY INITIALIZATION
 *
 * Implement lazy initialization to defer memory allocation.
 */

class LazyResource {
  constructor(initializeFn) {
    // TODO: Store initializer but don't call yet
  }

  get value() {
    // TODO: Initialize on first access
  }

  get isInitialized() {
    // TODO: Return whether resource has been initialized
  }

  reset() {
    // TODO: Clear initialized value
  }
}

// Test:
// const expensive = new LazyResource(() => {
//   console.log('Initializing expensive resource...');
//   return new Array(1000000).fill(0);
// });
//
// console.log(expensive.isInitialized); // false
// console.log(expensive.value.length);   // 1000000 (initializes here)
// console.log(expensive.isInitialized); // true

/**
 * EXERCISE 12: COMPREHENSIVE MEMORY MANAGEMENT
 *
 * Create a complete resource manager that handles multiple
 * types of cleanups.
 */

class ResourceManager {
  constructor() {
    // TODO: Initialize tracking structures
  }

  registerDisposable(resource, disposeFn) {
    // TODO: Track resource with its disposal function
  }

  registerInterval(intervalId) {
    // TODO: Track interval for cleanup
  }

  registerTimeout(timeoutId) {
    // TODO: Track timeout for cleanup
  }

  registerEventListener(element, event, handler) {
    // TODO: Track event listener for cleanup
  }

  dispose(resource) {
    // TODO: Dispose specific resource
  }

  disposeAll() {
    // TODO: Dispose all tracked resources
  }

  getStats() {
    // TODO: Return tracking statistics
  }
}

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

// Scroll down for solutions...

// SOLUTION 3: LRU Cache
class LRUCacheSolution {
  constructor(maxSize) {
    this.maxSize = maxSize;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) return undefined;
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }

  has(key) {
    return this.cache.has(key);
  }

  delete(key) {
    return this.cache.delete(key);
  }

  clear() {
    this.cache.clear();
  }

  get size() {
    return this.cache.size;
  }
}

// SOLUTION 4: Object Pool
class ObjectPoolSolution {
  constructor(createFn, resetFn, initialSize = 10) {
    this.createFn = createFn;
    this.resetFn = resetFn;
    this.pool = [];
    this.stats = {
      created: 0,
      acquired: 0,
      released: 0,
      active: 0,
    };

    for (let i = 0; i < initialSize; i++) {
      this.pool.push(this.createFn());
      this.stats.created++;
    }
  }

  acquire() {
    this.stats.acquired++;
    this.stats.active++;

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

    this.stats.created++;
    return this.createFn();
  }

  release(obj) {
    this.stats.released++;
    this.stats.active--;
    this.resetFn(obj);
    this.pool.push(obj);
  }

  drain() {
    this.pool.length = 0;
  }

  getStats() {
    return {
      ...this.stats,
      poolSize: this.pool.length,
    };
  }
}

// SOLUTION 5: WeakMap Metadata Store
class ObjectMetadataSolution {
  constructor() {
    this.store = new WeakMap();
  }

  setMeta(obj, key, value) {
    if (!this.store.has(obj)) {
      this.store.set(obj, new Map());
    }
    this.store.get(obj).set(key, value);
  }

  getMeta(obj, key) {
    const meta = this.store.get(obj);
    return meta?.get(key);
  }

  hasMeta(obj, key) {
    const meta = this.store.get(obj);
    return meta?.has(key) ?? false;
  }

  deleteMeta(obj, key) {
    const meta = this.store.get(obj);
    return meta?.delete(key) ?? false;
  }

  clearMeta(obj) {
    this.store.delete(obj);
  }
}

// SOLUTION 6: Break Circular References
function breakCircularReferencesSolution(obj, seen = new WeakSet()) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  if (seen.has(obj)) {
    return { $ref: 'circular' };
  }

  seen.add(obj);

  if (Array.isArray(obj)) {
    return obj.map((item) => breakCircularReferencesSolution(item, seen));
  }

  const result = {};
  for (const key of Object.keys(obj)) {
    result[key] = breakCircularReferencesSolution(obj[key], seen);
  }

  return result;
}

// SOLUTION 8: Circular Buffer
class CircularBufferSolution {
  constructor(capacity) {
    this._capacity = capacity;
    this.buffer = new Array(capacity);
    this.head = 0;
    this._length = 0;
  }

  push(item) {
    const index = (this.head + this._length) % this._capacity;
    this.buffer[index] = item;

    if (this._length < this._capacity) {
      this._length++;
    } else {
      this.head = (this.head + 1) % this._capacity;
    }
  }

  toArray() {
    const result = [];
    for (let i = 0; i < this._length; i++) {
      result.push(this.buffer[(this.head + i) % this._capacity]);
    }
    return result;
  }

  get length() {
    return this._length;
  }

  get capacity() {
    return this._capacity;
  }

  get isFull() {
    return this._length === this._capacity;
  }

  clear() {
    this.buffer.fill(undefined);
    this.head = 0;
    this._length = 0;
  }
}

// SOLUTION 11: Lazy Resource
class LazyResourceSolution {
  constructor(initializeFn) {
    this._initializeFn = initializeFn;
    this._value = undefined;
    this._initialized = false;
  }

  get value() {
    if (!this._initialized) {
      this._value = this._initializeFn();
      this._initialized = true;
    }
    return this._value;
  }

  get isInitialized() {
    return this._initialized;
  }

  reset() {
    this._value = undefined;
    this._initialized = false;
  }
}

console.log('Exercises loaded. Start solving!');
Exercises - JavaScript Tutorial | DeepML