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