javascript
exercises
exercises.js⚡javascript
/**
* ============================================
* 6.5 MEMORY MANAGEMENT - EXERCISES
* ============================================
*
* Practice understanding memory allocation,
* garbage collection, and avoiding memory leaks.
*/
/**
* EXERCISE 1: Stack vs Heap
* -------------------------
* Predict the outputs.
*/
console.log('=== Exercise 1: Stack vs Heap ===');
let num1 = 5;
let num2 = num1;
num2 = 10;
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
console.log('num1:', num1);
console.log('arr1:', arr1);
// YOUR PREDICTION:
// num1: ___
// arr1: ___
/*
* SOLUTION:
* num1: 5 (primitives are copied by value)
* arr1: [1, 2, 3, 4] (arrays are passed by reference)
*/
/**
* EXERCISE 2: Object Reference
* ----------------------------
* Will the original object be modified?
*/
console.log('\n=== Exercise 2: Object Reference ===');
function modifyObject(obj) {
obj.value = 100;
}
const myObj = { value: 1 };
modifyObject(myObj);
console.log('myObj.value:', myObj.value);
// YOUR PREDICTION: ___
/*
* SOLUTION:
* myObj.value: 100
* Objects are passed by reference, so modifications affect the original
*/
/**
* EXERCISE 3: Create a True Copy
* ------------------------------
* Modify this code so arr2 doesn't affect arr1.
*/
console.log('\n=== Exercise 3: Create True Copy ===');
const arr1Copy = [1, 2, 3];
// Change this line to create a true copy:
const arr2Copy = arr1Copy; // YOUR FIX HERE
arr2Copy.push(4);
// GOAL: arr1Copy should still be [1, 2, 3]
/*
* SOLUTION:
* const arr2Copy = [...arr1Copy];
* // OR
* const arr2Copy = arr1Copy.slice();
* // OR
* const arr2Copy = Array.from(arr1Copy);
*/
// Test with correct solution:
const arr1Fixed = [1, 2, 3];
const arr2Fixed = [...arr1Fixed];
arr2Fixed.push(4);
console.log('arr1Fixed:', arr1Fixed); // [1, 2, 3]
console.log('arr2Fixed:', arr2Fixed); // [1, 2, 3, 4]
/**
* EXERCISE 4: Identify the Memory Leak
* ------------------------------------
* What's wrong with this code?
*/
console.log('\n=== Exercise 4: Identify the Leak ===');
function createLeak() {
const elements = [];
function addElement() {
const el = { data: new Array(10000).fill('x') };
elements.push(el);
}
setInterval(addElement, 100);
}
// createLeak(); // DON'T RUN - will consume memory!
// QUESTION: What causes the memory leak?
// YOUR ANSWER: ___
/*
* SOLUTION:
* The 'elements' array keeps growing forever because:
* 1. setInterval runs indefinitely
* 2. Each call adds a large object to the array
* 3. Nothing ever clears the array or stops the interval
*
* FIX: Return the interval ID and clear it, or limit array size
*/
/**
* EXERCISE 5: Fix the Memory Leak
* -------------------------------
* Rewrite Exercise 4 to prevent the leak.
*/
console.log('\n=== Exercise 5: Fix the Leak ===');
function createNoLeak() {
// YOUR CODE HERE
// Requirements:
// 1. Limit the array to max 10 elements
// 2. Provide a way to stop the interval
}
/*
* SOLUTION:
*/
function createNoLeakSolution() {
const elements = [];
const MAX_ELEMENTS = 10;
function addElement() {
if (elements.length >= MAX_ELEMENTS) {
elements.shift(); // Remove oldest
}
const el = { data: new Array(100).fill('x') };
elements.push(el);
console.log('Elements count:', elements.length);
}
const intervalId = setInterval(addElement, 100);
// Return cleanup function
return () => {
clearInterval(intervalId);
console.log('Interval stopped, leak prevented');
};
}
const stop = createNoLeakSolution();
setTimeout(stop, 600); // Stop after a few iterations
/**
* EXERCISE 6: Closure Leak
* ------------------------
* How much memory does each closure hold?
*/
console.log('\n=== Exercise 6: Closure Memory ===');
function createHandlers() {
const largeData = new Array(100000).fill('x');
return {
handler1: () => largeData.length,
handler2: () => largeData[0],
handler3: () => 'static value',
};
}
const handlers = createHandlers();
// QUESTION: Which handlers keep largeData in memory?
// YOUR ANSWER: ___
/*
* SOLUTION:
* handler1 and handler2 both keep largeData in memory
* because they reference it.
*
* handler3 doesn't reference largeData but it's still kept
* because all closures from the same scope share the same
* variable environment.
*
* In practice, modern JS engines may optimize this.
*/
/**
* EXERCISE 7: WeakMap Use Case
* ----------------------------
* Implement a function to cache expensive calculations
* without preventing garbage collection.
*/
console.log('\n=== Exercise 7: WeakMap Cache ===');
// Implement a cache that doesn't prevent GC of objects
function createObjectCache() {
// YOUR CODE HERE
// Return an object with:
// - compute(obj): Returns cached result or computes new
}
/*
* SOLUTION:
*/
function createObjectCacheSolution() {
const cache = new WeakMap();
return {
compute(obj) {
if (cache.has(obj)) {
console.log('Cache hit!');
return cache.get(obj);
}
console.log('Computing...');
// Expensive computation
const result = JSON.stringify(obj).length;
cache.set(obj, result);
return result;
},
};
}
const cache = createObjectCacheSolution();
const testObj = { name: 'test', values: [1, 2, 3] };
console.log(cache.compute(testObj)); // Computing...
console.log(cache.compute(testObj)); // Cache hit!
/**
* EXERCISE 8: Event Listener Leak
* -------------------------------
* This component leaks memory. Fix it.
*/
console.log('\n=== Exercise 8: Event Listener Leak ===');
// LEAKY VERSION:
class LeakyCounter {
constructor() {
this.count = 0;
this.data = new Array(10000).fill('x');
// In browser: window.addEventListener('click', () => this.count++);
console.log('LeakyCounter: Listener added (simulated)');
}
// Missing: No way to remove the listener!
}
// YOUR FIX:
class FixedCounter {
constructor() {
this.count = 0;
this.data = new Array(10000).fill('x');
// YOUR CODE HERE
}
// Add cleanup method
}
/*
* SOLUTION:
*/
class FixedCounterSolution {
constructor() {
this.count = 0;
this.data = new Array(10000).fill('x');
// Bind the handler
this.handleClick = this.handleClick.bind(this);
// In browser: window.addEventListener('click', this.handleClick);
console.log('FixedCounter: Listener added (simulated)');
}
handleClick() {
this.count++;
}
destroy() {
// In browser: window.removeEventListener('click', this.handleClick);
this.data = null;
console.log('FixedCounter: Cleaned up');
}
}
const counter = new FixedCounterSolution();
counter.destroy();
/**
* EXERCISE 9: Circular Reference
* ------------------------------
* Will these objects be garbage collected?
*/
console.log('\n=== Exercise 9: Circular Reference ===');
function createCircularRef() {
const a = { name: 'A' };
const b = { name: 'B' };
a.ref = b;
b.ref = a;
return null; // Return nothing
}
createCircularRef();
// QUESTION: Will a and b be garbage collected?
// YOUR ANSWER: ___
/*
* SOLUTION:
* YES - Modern JavaScript engines use mark-and-sweep garbage collection.
*
* After createCircularRef() returns, there are no external references
* to either a or b. The GC starts from "roots" (global, stack) and
* marks reachable objects. Since a and b are not reachable from any
* root, they will both be collected, despite referencing each other.
*/
/**
* EXERCISE 10: Optimize String Operations
* ---------------------------------------
* This code is memory-inefficient. Optimize it.
*/
console.log('\n=== Exercise 10: String Optimization ===');
// INEFFICIENT:
function buildStringInefficient(n) {
let result = '';
for (let i = 0; i < n; i++) {
result += `item${i},`; // Creates new string each iteration!
}
return result;
}
// YOUR OPTIMIZED VERSION:
function buildStringEfficient(n) {
// YOUR CODE HERE
}
/*
* SOLUTION:
*/
function buildStringEfficientSolution(n) {
const parts = [];
for (let i = 0; i < n; i++) {
parts.push(`item${i}`);
}
return parts.join(',');
}
console.time('Inefficient');
buildStringInefficient(1000);
console.timeEnd('Inefficient');
console.time('Efficient');
buildStringEfficientSolution(1000);
console.timeEnd('Efficient');
/**
* EXERCISE 11: Implement Object Pool
* -----------------------------------
* Create a simple object pool for reuse.
*/
console.log('\n=== Exercise 11: Object Pool ===');
class ObjectPool {
constructor(createFn, initialSize = 5) {
// YOUR CODE HERE
// - Create initial pool of objects
// - Implement acquire() and release()
}
acquire() {
// Return an available object or null
}
release(obj) {
// Return object to pool
}
}
/*
* SOLUTION:
*/
class ObjectPoolSolution {
constructor(createFn, initialSize = 5) {
this.createFn = createFn;
this.available = [];
this.inUse = new Set();
// Pre-create objects
for (let i = 0; i < initialSize; i++) {
this.available.push(this.createFn());
}
}
acquire() {
let obj;
if (this.available.length > 0) {
obj = this.available.pop();
} else {
obj = this.createFn();
}
this.inUse.add(obj);
return obj;
}
release(obj) {
if (this.inUse.has(obj)) {
this.inUse.delete(obj);
this.available.push(obj);
}
}
get stats() {
return {
available: this.available.length,
inUse: this.inUse.size,
};
}
}
const pool = new ObjectPoolSolution(() => ({ x: 0, y: 0 }));
console.log('Initial:', pool.stats);
const obj1 = pool.acquire();
const obj2 = pool.acquire();
console.log('After acquiring 2:', pool.stats);
pool.release(obj1);
console.log('After releasing 1:', pool.stats);
/**
* EXERCISE 12: Find the Leak
* --------------------------
* This function has a subtle memory leak. Find it.
*/
console.log('\n=== Exercise 12: Find the Leak ===');
const globalHandlers = [];
function registerHandler(callback) {
const wrapper = {
data: new Array(1000).fill('x'),
callback: callback,
id: Date.now(),
};
globalHandlers.push(wrapper);
return wrapper.id;
}
// QUESTION: What's the memory leak here?
// YOUR ANSWER: ___
// How would you fix it?
// YOUR FIX: ___
/*
* SOLUTION:
* LEAK: globalHandlers array grows indefinitely. Every call to
* registerHandler adds an object that's never removed.
*
* FIX: Provide an unregister function:
*/
function unregisterHandler(id) {
const index = globalHandlers.findIndex((h) => h.id === id);
if (index !== -1) {
globalHandlers.splice(index, 1);
}
}
// Or use WeakMap if callbacks should be GC'd with their owners
/**
* EXERCISE 13: Memory-Conscious Data Processing
* ----------------------------------------------
* Process a large array without holding all results in memory.
*/
console.log('\n=== Exercise 13: Generator Processing ===');
// Process items one at a time using a generator
function* processLargeDataset(items) {
// YOUR CODE HERE
// Yield processed items one at a time
}
/*
* SOLUTION:
*/
function* processLargeDatasetSolution(items) {
for (const item of items) {
// Process each item
yield item * 2;
// Previous item can be GC'd before next is processed
}
}
const largeDataset = Array.from({ length: 10 }, (_, i) => i);
const processor = processLargeDatasetSolution(largeDataset);
console.log('Processing one at a time:');
for (const result of processor) {
console.log('Result:', result);
// Each iteration, only one result is in memory
}
/**
* EXERCISE 14: WeakSet for Visited Tracking
* -----------------------------------------
* Track visited nodes without preventing GC.
*/
console.log('\n=== Exercise 14: WeakSet Tracking ===');
function processTree(root) {
// Track visited nodes to avoid cycles
// Use WeakSet so nodes can be GC'd when tree is discarded
// YOUR CODE HERE
}
/*
* SOLUTION:
*/
function processTreeSolution(root) {
const visited = new WeakSet();
function visit(node) {
if (!node || visited.has(node)) return;
visited.add(node);
console.log('Visiting:', node.value);
for (const child of node.children || []) {
visit(child);
}
}
visit(root);
}
const tree = {
value: 'root',
children: [
{ value: 'child1', children: [] },
{ value: 'child2', children: [{ value: 'grandchild', children: [] }] },
],
};
processTreeSolution(tree);
/**
* EXERCISE 15: Memory Profiling Exercise
* --------------------------------------
* Write code to estimate memory usage of different structures.
*/
console.log('\n=== Exercise 15: Memory Estimation ===');
function estimateSize(label, createFn, count) {
if (typeof process === 'undefined' || !process.memoryUsage) {
console.log('Memory API not available');
return;
}
// Force GC if available
if (global.gc) global.gc();
const before = process.memoryUsage().heapUsed;
const items = [];
for (let i = 0; i < count; i++) {
items.push(createFn(i));
}
const after = process.memoryUsage().heapUsed;
const perItem = (after - before) / count;
console.log(`${label}: ~${Math.round(perItem)} bytes per item`);
return items; // Return to prevent optimization
}
// Compare different data structures
// Run with: node --expose-gc exercises.js
estimateSize('Empty object', () => ({}), 10000);
estimateSize('Small object', (i) => ({ id: i, name: 'test' }), 10000);
estimateSize('Array', (i) => [i, i + 1, i + 2], 10000);
estimateSize('Map entry', (i) => new Map([[i, i]]), 10000);
console.log('\n=== Exercises Complete ===');