javascript

exercises

exercises.js
/**
 * ========================================
 * 11.5 Timers and Scheduling - Exercises
 * ========================================
 *
 * Practice JavaScript timing and scheduling functions.
 * Complete each exercise by filling in the code.
 */

/**
 * EXERCISE 1: Countdown Timer
 *
 * Create a countdown timer.
 */

function createCountdown(seconds, onTick, onComplete) {
  // Call onTick every second with remaining time
  // Call onComplete when countdown reaches 0
  // Return cancel function
}

// Test:
// const cancel = createCountdown(5,
//     (remaining) => console.log(remaining),
//     () => console.log('Done!')
// );

/**
 * EXERCISE 2: Debounce Function
 *
 * Implement debounce with leading/trailing options.
 */

function debounce(fn, delay, options = {}) {
  // options: { leading: false, trailing: true }
  // leading: call immediately on first invocation
  // trailing: call after delay from last invocation
}

// Test:
// const debouncedLog = debounce(console.log, 300);
// debouncedLog('a'); debouncedLog('b'); debouncedLog('c');
// After 300ms: logs 'c'

/**
 * EXERCISE 3: Throttle Function
 *
 * Implement throttle with options.
 */

function throttle(fn, limit, options = {}) {
  // options: { leading: true, trailing: true }
  // leading: call on first invocation
  // trailing: call after limit if there were invocations
}

// Test:
// const throttledLog = throttle(console.log, 300);

/**
 * EXERCISE 4: Promise Delay
 *
 * Create async delay utilities.
 */

function delay(ms) {
  // Return promise that resolves after ms
}

function delayValue(ms, value) {
  // Return promise that resolves with value after ms
}

function timeout(promise, ms, message = 'Timeout') {
  // Race promise against timeout
  // Reject with message if timeout wins
}

// Test:
// await delay(1000);
// const result = await delayValue(500, 'hello');
// await timeout(slowOperation(), 5000);

/**
 * EXERCISE 5: Retry with Backoff
 *
 * Retry function with exponential backoff.
 */

async function retryWithBackoff(fn, options = {}) {
  // options: { maxRetries: 3, initialDelay: 1000, factor: 2 }
  // Double delay after each failure
  // Return result or throw after max retries
}

// Test:
// const result = await retryWithBackoff(
//     () => fetchData(),
//     { maxRetries: 3, initialDelay: 1000 }
// );

/**
 * EXERCISE 6: Polling Function
 *
 * Poll until condition is met.
 */

async function pollUntil(checkFn, options = {}) {
  // options: { interval: 1000, timeout: 30000 }
  // Keep calling checkFn until it returns truthy
  // Throw if timeout exceeded
}

// Test:
// const result = await pollUntil(
//     () => checkJobStatus(),
//     { interval: 2000, timeout: 60000 }
// );

/**
 * EXERCISE 7: Rate Limiter
 *
 * Limit function calls per time window.
 */

function createRateLimiter(maxCalls, windowMs) {
  // Return function that rate limits calls
  // Throws or queues if limit exceeded
}

// Test:
// const limited = createRateLimiter(5, 1000);
// for (let i = 0; i < 10; i++) {
//     await limited(() => api.call());
// }

/**
 * EXERCISE 8: Animation Frame Manager
 *
 * Manage requestAnimationFrame callbacks.
 */

class AnimationManager {
  constructor() {
    // Initialize
  }

  register(id, callback) {
    // Register animation callback with id
  }

  unregister(id) {
    // Remove animation by id
  }

  start() {
    // Start animation loop
  }

  stop() {
    // Stop animation loop
  }

  pause() {
    // Pause but keep registrations
  }

  resume() {
    // Resume from pause
  }
}

/**
 * EXERCISE 9: Scheduler
 *
 * Schedule tasks at specific times.
 */

class TaskScheduler {
  constructor() {
    // Initialize
  }

  scheduleAt(time, callback) {
    // Schedule callback at specific Date/time
    // Return task id
  }

  scheduleIn(delay, callback) {
    // Schedule callback after delay ms
    // Return task id
  }

  cancel(taskId) {
    // Cancel scheduled task
  }

  cancelAll() {
    // Cancel all tasks
  }
}

/**
 * EXERCISE 10: Interval with Pause
 *
 * Create pausable interval.
 */

function createPausableInterval(callback, interval) {
  // Return object with:
  // start(), pause(), resume(), stop(), isRunning()
}

// Test:
// const timer = createPausableInterval(() => console.log('tick'), 1000);
// timer.start();
// timer.pause();
// timer.resume();
// timer.stop();

/**
 * EXERCISE 11: Execution Timer
 *
 * Measure function execution time.
 */

async function measureExecution(fn) {
  // Return { result, duration, startTime, endTime }
}

function createProfiler() {
  // Return object that can track multiple measurements
  // { start(id), end(id), getStats(id), getAllStats() }
}

/**
 * EXERCISE 12: Queue with Concurrency
 *
 * Process queue with limited concurrency.
 */

class ConcurrencyQueue {
  constructor(concurrency = 1, delayBetween = 0) {
    // Initialize with max concurrent and delay between items
  }

  add(task) {
    // Add task to queue, return promise for result
  }

  pause() {
    // Pause processing (current tasks complete)
  }

  resume() {
    // Resume processing
  }

  clear() {
    // Clear pending tasks
  }
}

/**
 * EXERCISE 13: Idle Task Processor
 *
 * Process tasks during idle time.
 */

function createIdleProcessor(options = {}) {
  // options: { timeout: 1000, chunkTime: 5 }
  // Return { add(task), process(), clear(), pending() }
  // Use requestIdleCallback with fallback
}

/**
 * EXERCISE 14: Debounced Async
 *
 * Debounce for async functions.
 */

function debounceAsync(fn, delay) {
  // Like debounce but for async functions
  // Cancel pending promise when new call comes in
  // Return promise that resolves with latest result
}

// Test:
// const debouncedFetch = debounceAsync(fetchData, 300);
// const result = await debouncedFetch('query');

/**
 * EXERCISE 15: Cron-like Scheduler
 *
 * Schedule recurring tasks with patterns.
 */

class CronScheduler {
  constructor() {
    // Initialize
  }

  schedule(pattern, callback) {
    // Pattern: { minute, hour, dayOfWeek } or interval string
    // Support: 'every 5 minutes', 'daily at 10:00'
    // Return task id
  }

  unschedule(taskId) {
    // Remove scheduled task
  }

  getNextRun(taskId) {
    // Get next scheduled run time
  }
}

// ============================================
// SOLUTIONS (Hidden - Scroll to reveal)
// ============================================

/*
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 * SOLUTIONS BELOW
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

// SOLUTION 1: Countdown Timer
function createCountdownSolution(seconds, onTick, onComplete) {
  let remaining = seconds;

  const intervalId = setInterval(() => {
    remaining--;
    onTick(remaining);

    if (remaining <= 0) {
      clearInterval(intervalId);
      onComplete();
    }
  }, 1000);

  // Call immediately with initial value
  onTick(remaining);

  return () => clearInterval(intervalId);
}

// SOLUTION 2: Debounce Function
function debounceSolution(fn, delay, options = {}) {
  const { leading = false, trailing = true } = options;
  let timeoutId;
  let lastArgs;

  return function (...args) {
    lastArgs = args;
    const shouldCallNow = leading && !timeoutId;

    clearTimeout(timeoutId);

    timeoutId = setTimeout(() => {
      timeoutId = null;
      if (trailing && lastArgs) {
        fn.apply(this, lastArgs);
        lastArgs = null;
      }
    }, delay);

    if (shouldCallNow) {
      fn.apply(this, args);
      lastArgs = null;
    }
  };
}

// SOLUTION 3: Throttle Function
function throttleSolution(fn, limit, options = {}) {
  const { leading = true, trailing = true } = options;
  let waiting = false;
  let lastArgs = null;

  return function (...args) {
    if (!waiting) {
      if (leading) {
        fn.apply(this, args);
      } else {
        lastArgs = args;
      }

      waiting = true;

      setTimeout(() => {
        waiting = false;
        if (trailing && lastArgs) {
          fn.apply(this, lastArgs);
          lastArgs = null;
        }
      }, limit);
    } else {
      lastArgs = args;
    }
  };
}

// SOLUTION 4: Promise Delay
function delaySolution(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function delayValueSolution(ms, value) {
  return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}

function timeoutSolution(promise, ms, message = 'Timeout') {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error(message)), ms);
  });
  return Promise.race([promise, timeoutPromise]);
}

// SOLUTION 5: Retry with Backoff
async function retryWithBackoffSolution(fn, options = {}) {
  const { maxRetries = 3, initialDelay = 1000, factor = 2 } = options;
  let delay = initialDelay;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      await delaySolution(delay);
      delay *= factor;
    }
  }
}

// SOLUTION 6: Polling Function
async function pollUntilSolution(checkFn, options = {}) {
  const { interval = 1000, timeout = 30000 } = options;
  const startTime = Date.now();

  while (Date.now() - startTime < timeout) {
    const result = await checkFn();
    if (result) return result;
    await delaySolution(interval);
  }

  throw new Error('Polling timeout');
}

// SOLUTION 7: Rate Limiter
function createRateLimiterSolution(maxCalls, windowMs) {
  const calls = [];

  return async function execute(fn) {
    const now = Date.now();

    // Remove old calls outside window
    while (calls.length > 0 && calls[0] < now - windowMs) {
      calls.shift();
    }

    if (calls.length >= maxCalls) {
      // Wait until window allows
      const waitTime = calls[0] + windowMs - now;
      await delaySolution(waitTime);
      return execute(fn);
    }

    calls.push(now);
    return fn();
  };
}

// SOLUTION 8: Animation Frame Manager
class AnimationManagerSolution {
  constructor() {
    this.callbacks = new Map();
    this.frameId = null;
    this.running = false;
  }

  register(id, callback) {
    this.callbacks.set(id, callback);
  }

  unregister(id) {
    this.callbacks.delete(id);
    if (this.callbacks.size === 0) this.stop();
  }

  start() {
    if (this.running) return;
    this.running = true;

    const tick = (timestamp) => {
      for (const callback of this.callbacks.values()) {
        callback(timestamp);
      }

      if (this.running) {
        this.frameId = requestAnimationFrame(tick);
      }
    };

    this.frameId = requestAnimationFrame(tick);
  }

  stop() {
    this.running = false;
    if (this.frameId) {
      cancelAnimationFrame(this.frameId);
      this.frameId = null;
    }
  }

  pause() {
    this.running = false;
    cancelAnimationFrame(this.frameId);
  }

  resume() {
    if (!this.running && this.callbacks.size > 0) {
      this.start();
    }
  }
}

// SOLUTION 9: Scheduler
class TaskSchedulerSolution {
  constructor() {
    this.tasks = new Map();
    this.nextId = 1;
  }

  scheduleAt(time, callback) {
    const targetTime = time instanceof Date ? time.getTime() : time;
    const delay = Math.max(0, targetTime - Date.now());
    return this.scheduleIn(delay, callback);
  }

  scheduleIn(delay, callback) {
    const id = this.nextId++;
    const timeoutId = setTimeout(() => {
      this.tasks.delete(id);
      callback();
    }, delay);

    this.tasks.set(id, timeoutId);
    return id;
  }

  cancel(taskId) {
    const timeoutId = this.tasks.get(taskId);
    if (timeoutId) {
      clearTimeout(timeoutId);
      this.tasks.delete(taskId);
    }
  }

  cancelAll() {
    for (const [id, timeoutId] of this.tasks) {
      clearTimeout(timeoutId);
    }
    this.tasks.clear();
  }
}

// SOLUTION 10: Interval with Pause
function createPausableIntervalSolution(callback, interval) {
  let intervalId = null;
  let running = false;
  let remainingTime = interval;
  let lastTick = null;

  return {
    start() {
      if (running) return;
      running = true;
      lastTick = Date.now();
      intervalId = setInterval(() => {
        callback();
        lastTick = Date.now();
      }, interval);
    },

    pause() {
      if (!running) return;
      running = false;
      clearInterval(intervalId);
      remainingTime = interval - (Date.now() - lastTick);
    },

    resume() {
      if (running) return;
      running = true;

      setTimeout(() => {
        if (!running) return;
        callback();
        this.start();
      }, remainingTime);
    },

    stop() {
      running = false;
      clearInterval(intervalId);
      remainingTime = interval;
    },

    isRunning() {
      return running;
    },
  };
}

// SOLUTION 11: Execution Timer
async function measureExecutionSolution(fn) {
  const startTime = performance.now();
  const result = await fn();
  const endTime = performance.now();

  return {
    result,
    duration: endTime - startTime,
    startTime,
    endTime,
  };
}

function createProfilerSolution() {
  const measurements = new Map();

  return {
    start(id) {
      if (!measurements.has(id)) {
        measurements.set(id, { times: [], current: null });
      }
      measurements.get(id).current = performance.now();
    },

    end(id) {
      const data = measurements.get(id);
      if (data?.current) {
        data.times.push(performance.now() - data.current);
        data.current = null;
      }
    },

    getStats(id) {
      const data = measurements.get(id);
      if (!data || data.times.length === 0) return null;

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

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

    getAllStats() {
      const stats = {};
      for (const id of measurements.keys()) {
        stats[id] = this.getStats(id);
      }
      return stats;
    },
  };
}

// SOLUTION 12: Queue with Concurrency
class ConcurrencyQueueSolution {
  constructor(concurrency = 1, delayBetween = 0) {
    this.concurrency = concurrency;
    this.delayBetween = delayBetween;
    this.queue = [];
    this.running = 0;
    this.paused = false;
  }

  add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (
      this.paused ||
      this.running >= this.concurrency ||
      this.queue.length === 0
    ) {
      return;
    }

    this.running++;
    const { task, resolve, reject } = this.queue.shift();

    try {
      const result = await task();
      resolve(result);
    } catch (error) {
      reject(error);
    }

    if (this.delayBetween > 0) {
      await delaySolution(this.delayBetween);
    }

    this.running--;
    this.process();
  }

  pause() {
    this.paused = true;
  }

  resume() {
    this.paused = false;
    this.process();
  }

  clear() {
    this.queue.forEach(({ reject }) => reject(new Error('Queue cleared')));
    this.queue = [];
  }
}

// SOLUTION 13: Idle Task Processor
function createIdleProcessorSolution(options = {}) {
  const { timeout = 1000, chunkTime = 5 } = options;
  const tasks = [];
  let processing = false;

  const scheduleIdle = (callback) => {
    if (typeof requestIdleCallback !== 'undefined') {
      requestIdleCallback(callback, { timeout });
    } else {
      setTimeout(
        () =>
          callback({
            didTimeout: false,
            timeRemaining: () => chunkTime,
          }),
        1
      );
    }
  };

  const processLoop = (deadline) => {
    while (tasks.length > 0 && deadline.timeRemaining() > 0) {
      const task = tasks.shift();
      task();
    }

    if (tasks.length > 0) {
      scheduleIdle(processLoop);
    } else {
      processing = false;
    }
  };

  return {
    add(task) {
      tasks.push(task);
    },

    process() {
      if (!processing && tasks.length > 0) {
        processing = true;
        scheduleIdle(processLoop);
      }
    },

    clear() {
      tasks.length = 0;
    },

    pending() {
      return tasks.length;
    },
  };
}

// SOLUTION 14: Debounced Async
function debounceAsyncSolution(fn, delay) {
  let timeoutId;
  let currentPromise;
  let currentResolve;
  let currentReject;

  return function (...args) {
    // Reject previous promise if pending
    if (currentReject) {
      currentReject(new Error('Debounced'));
    }

    // Create new promise
    currentPromise = new Promise((resolve, reject) => {
      currentResolve = resolve;
      currentReject = reject;
    });

    clearTimeout(timeoutId);

    timeoutId = setTimeout(async () => {
      try {
        const result = await fn.apply(this, args);
        currentResolve(result);
      } catch (error) {
        currentReject(error);
      }
      currentResolve = currentReject = null;
    }, delay);

    return currentPromise;
  };
}

// SOLUTION 15: Cron-like Scheduler (simplified)
class CronSchedulerSolution {
  constructor() {
    this.tasks = new Map();
    this.nextId = 1;
  }

  schedule(pattern, callback) {
    const id = this.nextId++;
    let intervalMs;

    if (typeof pattern === 'string') {
      // Parse simple patterns
      const match = pattern.match(/every (\d+) (minute|second|hour)s?/i);
      if (match) {
        const value = parseInt(match[1]);
        const unit = match[2].toLowerCase();
        const multipliers = { second: 1000, minute: 60000, hour: 3600000 };
        intervalMs = value * multipliers[unit];
      }
    } else if (typeof pattern === 'object') {
      // Calculate interval from pattern
      intervalMs = (pattern.minute || 1) * 60000;
    }

    if (intervalMs) {
      const intervalId = setInterval(callback, intervalMs);
      this.tasks.set(id, { intervalId, intervalMs, callback });
    }

    return id;
  }

  unschedule(taskId) {
    const task = this.tasks.get(taskId);
    if (task) {
      clearInterval(task.intervalId);
      this.tasks.delete(taskId);
    }
  }

  getNextRun(taskId) {
    const task = this.tasks.get(taskId);
    if (!task) return null;
    return new Date(Date.now() + task.intervalMs);
  }
}

console.log('Timer and Scheduling exercises loaded!');
console.log(
  'Complete each exercise and check against solutions at the bottom.'
);
Exercises - JavaScript Tutorial | DeepML