javascript

exercises

exercises.js
/**
 * ============================================
 * 6.2 THE CALL STACK - EXERCISES
 * ============================================
 *
 * Practice understanding how the call stack works,
 * tracing function calls, and debugging.
 */

/**
 * EXERCISE 1: Trace the Stack
 * ---------------------------
 * Write out the call stack at each step.
 */

console.log('=== Exercise 1: Trace the Stack ===');

function alpha() {
  console.log('A');
  beta();
  console.log('D');
}

function beta() {
  console.log('B');
  gamma();
  console.log('C');
}

function gamma() {
  console.log('*');
}

alpha();

// YOUR ANSWER:
// Draw the stack at the point where "*" is logged:
// ┌─────────────────┐
// │     ???         │
// ├─────────────────┤
// │     ???         │
// ├─────────────────┤
// │     ???         │
// ├─────────────────┤
// │     ???         │
// └─────────────────┘

/*
 * SOLUTION:
 * When "*" is logged:
 * ┌─────────────────┐
 * │     gamma()     │ ← Currently executing
 * ├─────────────────┤
 * │     beta()      │
 * ├─────────────────┤
 * │     alpha()     │
 * ├─────────────────┤
 * │     Global      │
 * └─────────────────┘
 *
 * Output order: A, B, *, C, D
 */

/**
 * EXERCISE 2: Predict Output Order
 * --------------------------------
 * Number the console.log statements in order of execution.
 */

console.log('\n=== Exercise 2: Predict Output Order ===');

function outer() {
  console.log('A'); // Order: ___
  inner();
  console.log('B'); // Order: ___
}

function inner() {
  console.log('C'); // Order: ___
}

console.log('D'); // Order: ___
outer();
console.log('E'); // Order: ___

// YOUR ANSWER: D, A, C, B, E

/*
 * SOLUTION:
 * Order: D (1), A (2), C (3), B (4), E (5)
 *
 * D is logged first (global code)
 * outer() is called, logs A
 * inner() is called from outer(), logs C
 * inner() returns, outer() continues, logs B
 * outer() returns, global continues, logs E
 */

/**
 * EXERCISE 3: Stack at Specific Point
 * ------------------------------------
 * What's on the stack when "CHECKPOINT" is logged?
 */

console.log('\n=== Exercise 3: Stack at Checkpoint ===');

function one() {
  two();
}

function two() {
  three();
}

function three() {
  four();
}

function four() {
  console.log('CHECKPOINT');
}

one();

// Write the stack from bottom to top:
// 1. (bottom) ___
// 2. ___
// 3. ___
// 4. ___
// 5. (top) ___

/*
 * SOLUTION:
 * 1. (bottom) Global
 * 2. one()
 * 3. two()
 * 4. three()
 * 5. (top) four() ← executing console.log
 */

/**
 * EXERCISE 4: Return Value Path
 * -----------------------------
 * Trace how the return value flows back through the stack.
 */

console.log('\n=== Exercise 4: Return Value Path ===');

function multiply(x, y) {
  return x * y;
}

function add(a, b) {
  return a + b;
}

function calculate(n) {
  const doubled = multiply(n, 2);
  const final = add(doubled, 10);
  return final;
}

const answer = calculate(5);
console.log('Answer:', answer);

// Trace the return values:
// calculate(5) is called
// multiply(5, 2) returns ___
// add(___, 10) returns ___
// calculate(5) returns ___

/*
 * SOLUTION:
 * multiply(5, 2) returns 10
 * add(10, 10) returns 20
 * calculate(5) returns 20
 */

/**
 * EXERCISE 5: Fix the Stack Overflow
 * ----------------------------------
 * This function causes a stack overflow. Fix it!
 */

console.log('\n=== Exercise 5: Fix Stack Overflow ===');

// BROKEN - causes stack overflow
function countdownBroken(n) {
  console.log(n);
  countdownBroken(n - 1); // No stopping condition!
}

// countdownBroken(5); // DON'T RUN THIS!

// YOUR FIX:
function countdownFixed(n) {
  // Add proper base case
  // YOUR CODE HERE
}

// countdownFixed(5);

/*
 * SOLUTION:
 */
function countdownFixedSolution(n) {
  if (n < 0) return; // Base case!
  console.log(n);
  countdownFixedSolution(n - 1);
}

countdownFixedSolution(5);

/**
 * EXERCISE 6: Recursion Stack Depth
 * ---------------------------------
 * How deep does the stack get for each call?
 */

console.log('\n=== Exercise 6: Recursion Stack Depth ===');

function sum(n) {
  if (n <= 0) return 0;
  return n + sum(n - 1);
}

// For sum(5), what's the maximum stack depth (not counting global)?
// YOUR ANSWER: ___

// Trace:
// sum(5) calls sum(4)
// sum(4) calls sum(3)
// sum(3) calls sum(2)
// sum(2) calls sum(1)
// sum(1) calls sum(0)
// sum(0) returns 0

/*
 * SOLUTION:
 * Maximum stack depth: 6 (sum(5), sum(4), sum(3), sum(2), sum(1), sum(0))
 * Or 5 if not counting the base case that immediately returns.
 */

console.log('sum(5) =', sum(5));

/**
 * EXERCISE 7: Async vs Sync Stack
 * -------------------------------
 * Predict the output order.
 */

console.log('\n=== Exercise 7: Async vs Sync ===');

function sync1() {
  console.log('Sync 1');
}

function sync2() {
  console.log('Sync 2');
}

console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
sync1();
setTimeout(() => console.log('Timeout 2'), 0);
sync2();
console.log('End');

// YOUR PREDICTED ORDER:
// 1. ___
// 2. ___
// 3. ___
// 4. ___
// 5. ___
// 6. ___

/*
 * SOLUTION:
 * 1. Start
 * 2. Sync 1
 * 3. Sync 2
 * 4. End
 * 5. Timeout 1
 * 6. Timeout 2
 *
 * Sync code runs first (in stack), then callbacks (from queue)
 */

/**
 * EXERCISE 8: Read the Stack Trace
 * --------------------------------
 * Interpret this error stack trace.
 */

console.log('\n=== Exercise 8: Read Stack Trace ===');

function parseData(data) {
  return processData(data);
}

function processData(data) {
  return transformData(data);
}

function transformData(data) {
  if (!data) {
    throw new Error('No data provided!');
  }
  return data.toUpperCase();
}

try {
  parseData(null);
} catch (e) {
  console.log('Stack trace:');
  console.log(e.stack);
}

// Questions:
// 1. Which function threw the error? ___
// 2. What was the call chain? ___ → ___ → ___

/*
 * SOLUTION:
 * 1. transformData threw the error
 * 2. Call chain: parseData → processData → transformData
 */

/**
 * EXERCISE 9: Write console.trace() Strategically
 * ------------------------------------------------
 * Add console.trace() calls to help debug this code.
 */

console.log('\n=== Exercise 9: Strategic console.trace() ===');

function handleRequest(req) {
  validateRequest(req);
  return processRequest(req);
}

function validateRequest(req) {
  if (!req.id) {
    // Where would you add console.trace() to see how we got here?
    console.log('Invalid request!');
  }
}

function processRequest(req) {
  return { processed: true, id: req.id };
}

// Test with invalid request
handleRequest({});

// YOUR TASK: Add console.trace() to validateRequest to debug

/*
 * SOLUTION:
 */
function validateRequestWithTrace(req) {
  if (!req.id) {
    console.trace('Validation failed - call stack:');
    console.log('Invalid request!');
  }
}

function handleRequestWithTrace(req) {
  validateRequestWithTrace(req);
  return processRequest(req);
}

handleRequestWithTrace({});

/**
 * EXERCISE 10: Maximum Stack Depth Calculator
 * -------------------------------------------
 * Write a function to find the approximate max stack depth.
 */

console.log('\n=== Exercise 10: Max Stack Depth ===');

function findMaxStackDepth() {
  // YOUR CODE HERE
  // Hint: Use recursion with try-catch
}

/*
 * SOLUTION:
 */
function findMaxStackDepthSolution() {
  let depth = 0;

  function dive() {
    depth++;
    dive();
  }

  try {
    dive();
  } catch (e) {
    return depth;
  }
}

console.log('Approximate max stack depth:', findMaxStackDepthSolution());

/**
 * EXERCISE 11: Convert Recursive to Iterative
 * -------------------------------------------
 * This recursive function can overflow on large inputs.
 * Convert it to iterative to fix.
 */

console.log('\n=== Exercise 11: Convert to Iterative ===');

// Recursive version (can overflow for large n)
function sumToN_recursive(n) {
  if (n <= 0) return 0;
  return n + sumToN_recursive(n - 1);
}

// YOUR TASK: Write an iterative version
function sumToN_iterative(n) {
  // YOUR CODE HERE
}

/*
 * SOLUTION:
 */
function sumToN_iterativeSolution(n) {
  let total = 0;
  for (let i = 1; i <= n; i++) {
    total += i;
  }
  return total;
}

console.log('Recursive sum(10):', sumToN_recursive(10));
console.log('Iterative sum(10):', sumToN_iterativeSolution(10));
// Iterative can handle sumToN(100000) without overflow!

/**
 * EXERCISE 12: Stack Frame Visualization
 * --------------------------------------
 * Implement a function that visualizes the call stack.
 */

console.log('\n=== Exercise 12: Stack Visualization ===');

const stackViz = [];

function visualizePush(name) {
  stackViz.push(name);
  console.log(`PUSH: ${name}`);
  console.log('Stack:', JSON.stringify(stackViz));
}

function visualizePop() {
  const name = stackViz.pop();
  console.log(`POP: ${name}`);
  console.log('Stack:', JSON.stringify(stackViz));
}

// YOUR TASK: Create a wrapper that auto-tracks calls
function withStackTracking(fn, name) {
  return function (...args) {
    // YOUR CODE HERE
  };
}

/*
 * SOLUTION:
 */
function withStackTrackingSolution(fn, name) {
  return function (...args) {
    visualizePush(name);
    const result = fn.apply(this, args);
    visualizePop();
    return result;
  };
}

// Test it
const trackedFunc = withStackTrackingSolution(() => {
  console.log('  (function executing)');
  return 42;
}, 'myFunction');

trackedFunc();

/**
 * EXERCISE 13: Nested Callback Stack
 * -----------------------------------
 * Trace the stack for nested callbacks.
 */

console.log('\n=== Exercise 13: Nested Callbacks ===');

function step1(callback) {
  console.log('Step 1');
  callback();
}

function step2(callback) {
  console.log('Step 2');
  callback();
}

function step3() {
  console.log('Step 3 - Top of stack!');
  console.trace('Stack at step3:');
}

// What's the stack when step3 executes?
step1(() => {
  step2(() => {
    step3();
  });
});

// YOUR ANSWER (bottom to top):
// ___
// ___
// ___
// ___

/*
 * SOLUTION:
 * Global
 * step1
 * (anonymous callback 1)
 * step2
 * (anonymous callback 2)
 * step3 ← top
 */

/**
 * EXERCISE 14: Tail Recursion
 * ---------------------------
 * Rewrite this function to be tail-recursive.
 */

console.log('\n=== Exercise 14: Tail Recursion ===');

// Not tail-recursive (has pending multiplication)
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1); // Not in tail position
}

// YOUR TASK: Make it tail-recursive
function factorialTail(n, accumulator = 1) {
  // YOUR CODE HERE
}

/*
 * SOLUTION:
 */
function factorialTailSolution(n, accumulator = 1) {
  if (n <= 1) return accumulator;
  return factorialTailSolution(n - 1, n * accumulator); // Tail call!
}

console.log('factorial(5):', factorial(5));
console.log('factorialTail(5):', factorialTailSolution(5));

/**
 * EXERCISE 15: Debug This Stack Issue
 * ------------------------------------
 * This code has a subtle stack-related bug. Find and fix it.
 */

console.log('\n=== Exercise 15: Debug Stack Issue ===');

let operations = 0;

function processItem(item, items, index) {
  operations++;
  console.log(`Processing item ${index}: ${item}`);

  if (index < items.length - 1) {
    // BUG: What's wrong here?
    processItem(items[index + 1], items, index + 1);
  }

  return true;
}

const items = ['a', 'b', 'c', 'd', 'e'];
operations = 0;
processItem(items[0], items, 0);
console.log('Total operations:', operations);

// Question: This works fine for 5 items.
// What happens with 50,000 items? ___

// YOUR TASK: Rewrite to avoid potential stack overflow
function processItemsSafe(items) {
  // YOUR CODE HERE
}

/*
 * SOLUTION:
 * Problem: For 50,000 items, we'd have 50,000 stack frames → overflow!
 *
 * Fix: Use iteration instead
 */
function processItemsSafeSolution(items) {
  for (let i = 0; i < items.length; i++) {
    console.log(`Processing item ${i}: ${items[i]}`);
  }
}

console.log('\n=== Exercises Complete ===');
console.log('Review the stack traces and compare with solutions!');
Exercises - JavaScript Tutorial | DeepML