javascript
exercises
exercises.js⚡javascript
/**
* ============================================================
* 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!');