40 Advanced JavaScript Coding Exercises
Deep Clone an Object Using Recursion
Objective: Create a function that performs a deep clone of an object, handling nested objects and arrays.
function deepClone(obj) {
if (obj === null || typeof obj !== ‘object’) {
return obj;
}
const clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
Explanation: This function uses recursion to iterate through each property of the object,
creating copies of nested objects and arrays to ensure a true deep copy is made.
Custom Array Filter Function
Objective: Implement your own version of the “filter” method for arrays.
Array.prototype.customFilter = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
Explanation: The “customFilter” method takes a callback function, iterates through the array,
and only adds elements to the result if the callback returns true for that element.
Implement a Simple Promise
Objective: Create a simplified version of a Promise constructor.
class SimplePromise {
constructor(executor) {
this.state = ‘pending’;
this.value = undefined;
this.handlers = [];
const resolve = (value) => {
if (this.state !== ‘pending’) return;
this.state = ‘fulfilled’;
this.value = value;
this.handlers.forEach((handler) => handler(value));
};
executor(resolve);
}
then(callback) {
if (this.state === ‘fulfilled’) {
callback(this.value);
} else {
this.handlers.push(callback);
}
}
}
Explanation: This implementation mimics how a real promise works with basic “then” functionality and state management.
Flatten a Nested Array
Objective: Write a function to flatten an array of arbitrarily nested arrays.
function flattenArray(arr) {
return arr.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? flattenArray(val) : val);
}, []);
}
Explanation: This function uses recursion to flatten each nested array it encounters, effectively reducing the depth to one level.
Debounce Function
Objective: Implement a debounce function to limit how often a function can run.
function debounce(func, delay) {
let timer;
return function (…args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
Explanation: Debounce delays the execution of the function until after a specified wait time,
which is useful for event handling like key presses or window resizing.
Throttle Function
Objective: Implement a throttle function to ensure a function runs at most once every specified time.
function throttle(func, limit) {
let lastCall = 0;
return function (…args) {
const now = Date.now();
if (now – lastCall >= limit) {
lastCall = now;
func.apply(this, args);
}
};
}
Explanation: Throttle restricts a function from being called more than once in a set period of time.
Event Delegation
Objective: Implement event delegation to handle clicks on dynamically generated list items.
document.querySelector(‘#list’).addEventListener(‘click’, function (event) {
if (event.target && event.target.nodeName === ‘LI’) {
console.log(‘List item’, event.target.textContent, ‘was clicked!’);
}
});
Explanation: Instead of attaching event listeners to each list item, this attaches a single listener to the parent,
using the event object to check the clicked target.
Curry a Function
Objective: Implement a function that curries another function.
function curry(func) {
return function curried(…args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function (…nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}
Explanation: The curried function keeps returning functions until it has received all arguments it needs to call the original function.
Memoization Function
Objective: Implement a function that memoizes another function’s output.
function memoize(fn) {
const cache = {};
return function (…args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
} else {
const result = fn.apply(this, args);
cache[key] = result;
return result;
}
};
}
Explanation: The “memoize” function caches the results of previous calls to save computation time when called with the same arguments again.
Implement Your Own “bind” Function
Objective: Create a polyfill for the “Function.prototype.bind” method.
Function.prototype.customBind = function (context, …args) {
const func = this;
return function (…newArgs) {
return func.apply(context, args.concat(newArgs));
};
};
Explanation: This “customBind” function captures the original function and returns a new function with the “context” and initial arguments
bound to it, just like the native “bind” method in JavaScript.
Implement a Retry Function
Objective: Write a function that retries a given asynchronous function a specified number of times.
function retry(fn, retries) {
return async function (…args) {
let attempt = 0;
while (attempt < retries) {
try {
return await fn(…args);
} catch (error) {
attempt++;
if (attempt >= retries) throw error;
}
}
};
}
Explanation: This function will attempt to execute the given asynchronous function until it either succeeds or reaches the specified retry limit.
Deep Freeze an Object
Objective: Implement a function that deeply freezes an object.
function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === ‘object’ && obj[key] !== null && !Object.isFrozen(obj[key])) {
deepFreeze(obj[key]);
}
});
}
Explanation: This function recursively freezes each property of the object to prevent any future modifications.
Pipeline Function
Objective: Implement a function that performs left-to-right function composition.
function pipeline(…fns) {
return (input) => fns.reduce((acc, fn) => fn(acc), input);
}
Explanation: The “pipeline” function allows for the sequential application of multiple functions, making code more readable and modular.
Async Parallel Map
Objective: Create a function that runs async operations in parallel on an array.
async function asyncParallelMap(arr, asyncCallback) {
return Promise.all(arr.map(asyncCallback));
}
Explanation: This function takes an array and an asynchronous callback and runs all operations concurrently using “Promise.all”.
Custom Reduce Function
Objective: Implement your own version of the “reduce” method for arrays.
Array.prototype.customReduce = function (callback, initialValue) {
let accumulator = initialValue !== undefined ? initialValue : this[0];
let startIndex = initialValue !== undefined ? 0 : 1;
for (let i = startIndex; i < this.length; i++) {
accumulator = callback(accumulator, this[i], i, this);
}
return accumulator;
};
Explanation: The “customReduce” method processes each array element and accumulates the result based on the provided callback.
Rate Limiter
Objective: Implement a rate limiter to control how frequently a function can be executed.
function rateLimiter(func, limit, timeWindow) {
let callTimes = [];
return function (…args) {
const now = Date.now();
callTimes = callTimes.filter((time) => now – time < timeWindow);
if (callTimes.length < limit) {
callTimes.push(now);
func.apply(this, args);
}
};
}
Explanation: This function restricts the number of times a function can be called within a given timeframe.
Implement a Lazy Evaluation Function
Objective: Create a function that returns a lazy-evaluated value.
function lazy(fn) {
let evaluated = false;
let value;
return function () {
if (!evaluated) {
value = fn();
evaluated = true;
}
return value;
};
}
Explanation: The “lazy” function delays the execution of the provided function until it is needed, and caches the result for future use.
Custom Event Emitter
Objective: Implement a basic event emitter class.
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
emit(event, …args) {
if (this.events[event]) {
this.events[event].forEach((listener) => listener(…args));
}
}
}
Explanation: The “EventEmitter” class allows you to add event listeners and emit events, similar to how Node.js handles events.
Implement a Custom Map Function for Objects
Objective: Implement a function that maps over an object’s values.
function mapObject(obj, callback) {
const result = {};
Object.keys(obj).forEach((key) => {
result[key] = callback(obj[key], key, obj);
});
return result;
}
Explanation: The “mapObject” function allows you to transform each value of an object based on a callback function.
Implement a Custom SetInterval with Pause and Resume
Objective: Create a “setInterval” function that can be paused and resumed.
function createInterval(func, delay) {
let timer;
let remaining = delay;
let start;
const pause = () => {
clearTimeout(timer);
remaining -= Date.now() – start;
};
const resume = () => {
start = Date.now();
timer = setTimeout(function tick() {
func();
start = Date.now();
timer = setTimeout(tick, delay);
}, remaining);
};
resume();
return { pause, resume };
}
Explanation: The “createInterval” function wraps a setTimeout loop and allows pausing and resuming the interval based on user interaction.
Implement a Binary Search Function
Objective: Write a function that performs a binary search on a sorted array.
function binarySearch(arr, target) {
let left = 0;
let right = arr.length – 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid – 1;
}
}
return -1;
}
Explanation: This function uses a divide-and-conquer approach to efficiently search for an element in a sorted array.
Promisify a Callback Function
Objective: Write a function that converts a callback-based function to return a promise.
function promisify(fn) {
return function (…args) {
return new Promise((resolve, reject) => {
fn(…args, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};
}
Explanation: This function transforms a traditional callback-based function into a function that returns a promise, making it easier to work with async code.
Implement a Basic LRU Cache
Objective: Create a Least Recently Used (LRU) cache class.
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return -1;
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
this.cache.delete(this.cache.keys().next().value);
}
this.cache.set(key, value);
}
}
Explanation: The “LRUCache” class keeps track of the most recently used items and evicts the least recently used item when the cache reaches capacity.
Implement a Simple Pub/Sub Pattern
Objective: Write a basic implementation of the Publish/Subscribe pattern.
class PubSub {
constructor() {
this.topics = {};
}
subscribe(topic, listener) {
if (!this.topics[topic]) {
this.topics[topic] = [];
}
this.topics[topic].push(listener);
}
publish(topic, data) {
if (this.topics[topic]) {
this.topics[topic].forEach((listener) => listener(data));
}
}
}
Explanation: The “PubSub” class allows different parts of your application to communicate without being directly connected by subscribing to and publishing events.
Implement a Custom Promise.all
Objective: Create your own version of “Promise.all” that resolves when all promises are fulfilled or rejects if any fail.
function customPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((value) => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
})
.catch((error) => reject(error));
});
});
}
Explanation: This function takes an array of promises and resolves only when all promises are fulfilled, similar to the behavior of “Promise.all”.
Implement a Custom Observer Pattern
Objective: Implement an Observer class where observers can be added, removed, and notified.
class Observer {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
this.observers = this.observers.filter((obs) => obs !== observer);
}
notify(data) {
this.observers.forEach((observer) => observer.update(data));
}
}
Explanation: The “Observer” class maintains a list of observers and can notify them when something changes, making it suitable for implementing reactive systems.
Implement a Sleep Function
Objective: Write a function that delays the execution of code for a specified number of milliseconds.
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Explanation: The “sleep” function returns a promise that resolves after a specified delay, allowing for a pause in asynchronous code execution.
Implement a Singleton Pattern
Objective: Create a Singleton class that ensures only one instance of the class is created.
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
this.data = “I am the singleton instance”;
}
}
Explanation: The “Singleton” class ensures that only one instance of the class can exist, returning the existing instance if it has already been created.
Implement a Fibonacci Sequence Generator Using Generator Functions
Objective: Create a generator function that yields Fibonacci numbers indefinitely.
function* fibonacciGenerator() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
Explanation: This generator function produces Fibonacci numbers indefinitely, allowing the consumer to decide how many numbers to retrieve.
Implement an Async Queue
Objective: Create a queue that processes asynchronous tasks one at a time.
class AsyncQueue {
constructor() {
this.queue = [];
this.processing = false;
}
enqueue(task) {
this.queue.push(task);
this.processNext();
}
async processNext() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const task = this.queue.shift();
await task();
this.processing = false;
this.processNext();
}
}
Explanation: The “AsyncQueue” ensures that only one asynchronous task is processed at a time, making it useful for rate-limited operations.
Implement a Custom Event Handler
Objective: Create a simple event handler that allows registration, removal, and triggering of events.
class EventHandler {
constructor() {
this.events = {};
}
on(event, handler) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(handler);
}
off(event, handler) {
if (this.events[event]) {
this.events[event] = this.events[event].filter((h) => h !== handler);
}
}
trigger(event, …args) {
if (this.events[event]) {
this.events[event].forEach((handler) => handler(…args));
}
}
}
Explanation: This “EventHandler” class allows for managing event listeners, similar to how events work in the DOM.
Implement a Custom Retry with Backoff
Objective: Write a function that retries an async function with exponential backoff.
async function retryWithBackoff(fn, retries, delay) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries – 1) throw error;
await sleep(delay * Math.pow(2, i));
}
}
}
Explanation: This function retries an async operation with a delay that doubles each time, allowing for exponential backoff in case of failure.
Implement a Circular Queue
Objective: Write a circular queue class that wraps around when reaching the end.
class CircularQueue {
constructor(size) {
this.queue = new Array(size);
this.head = -1;
this.tail = -1;
this.maxSize = size;
}
enqueue(value) {
if ((this.tail + 1) % this.maxSize === this.head) {
throw new Error(“Queue is full”);
}
if (this.head === -1) {
this.head = 0;
}
this.tail = (this.tail + 1) % this.maxSize;
this.queue[this.tail] = value;
}
dequeue() {
if (this.head === -1) {
throw new Error(“Queue is empty”);
}
const value = this.queue[this.head];
if (this.head === this.tail) {
this.head = this.tail = -1;
} else {
this.head = (this.head + 1) % this.maxSize;
}
return value;
}
}
Explanation: The “CircularQueue” class allows elements to be added and removed in a circular fashion, preventing the need for array resizing.
Implement a Custom Set Data Structure
Objective: Create a custom set class that supports adding, deleting, and checking for elements.
class CustomSet {
constructor() {
this.items = {};
}
add(value) {
this.items[value] = true;
}
delete(value) {
delete this.items[value];
}
has(value) {
return this.items.hasOwnProperty(value);
}
}
Explanation: The “CustomSet” class is similar to JavaScript’s native Set, allowing the storage of unique values and basic set operations.
Implement a Binary Search Tree
Objective: Create a class to represent a Binary Search Tree (BST).
class BST {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
insert(value) {
if (value < this.value) {
if (this.left === null) {
this.left = new BST(value);
} else {
this.left.insert(value);
}
} else {
if (this.right === null) {
this.right = new BST(value);
} else {
this.right.insert(value);
}
}
}
contains(value) {
if (value === this.value) return true;
if (value < this.value) {
return this.left ? this.left.contains(value) : false;
} else {
return this.right ? this.right.contains(value) : false;
}
}
}
Explanation: The “BST” class allows you to insert values into the binary search tree and check if the tree contains a specific value.
Implement a Priority Queue
Objective: Create a priority queue that dequeues elements based on priority.
class PriorityQueue {
constructor() {
this.queue = [];
}
enqueue(element, priority) {
const node = { element, priority };
let added = false;
for (let i = 0; i < this.queue.length; i++) {
if (node.priority < this.queue[i].priority) {
this.queue.splice(i, 0, node);
added = true;
break;
}
}
if (!added) {
this.queue.push(node);
}
}
dequeue() {
return this.queue.shift();
}
}
Explanation: The “PriorityQueue” class maintains the elements in an order such that the highest-priority element is dequeued first.
Implement a Merge Sort Algorithm
Objective: Write a function that sorts an array using the merge sort algorithm.
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(left, right) {
const result = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
Explanation: The “mergeSort” function splits the array in half recursively and merges sorted halves to sort the entire array.
Implement a Function Composition Utility
Objective: Create a function that allows for right-to-left function composition.
function compose(…fns) {
return (input) => fns.reduceRight((acc, fn) => fn(acc), input);
}
Explanation: The “compose” function allows for the application of multiple functions in a right-to-left order, similar to function piping in functional programming.
Implement a Basic Bloom Filter
Objective: Create a simple Bloom Filter for membership testing.
class BloomFilter {
constructor(size) {
this.size = size;
this.storage = new Array(size).fill(false);
}
hash(value) {
let hash = 0;
for (let i = 0; i < value.length; i++) {
hash = (hash << 5) – hash + value.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash % this.size);
}
add(value) {
const index = this.hash(value);
this.storage[index] = true;
}
contains(value) {
const index = this.hash(value);
return this.storage[index];
}
}
Explanation: The “BloomFilter” class is used to test if an element is a member of a set with a possible false positive rate, making it useful for membership testing.
Implement a Max Heap
Objective: Write a class that represents a max heap, allowing insertion and extraction of the maximum element.
class MaxHeap {
constructor() {
this.heap = [];
}
insert(value) {
this.heap.push(value);
this.bubbleUp(this.heap.length – 1);
}
extractMax() {
if (this.heap.length === 0) return null;
if (this.heap.length === 1) return this.heap.pop();
const max = this.heap[0];
this.heap[0] = this.heap.pop();
this.bubbleDown(0);
return max;
}
bubbleUp(index) {
const parentIndex = Math.floor((index – 1) / 2);
if (parentIndex >= 0 && this.heap[parentIndex] < this.heap[index]) {
[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
this.bubbleUp(parentIndex);
}
}
bubbleDown(index) {
const leftChildIndex = 2 * index + 1;
const rightChildIndex = 2 * index + 2;
let largest = index;
if (leftChildIndex < this.heap.length && this.heap[leftChildIndex] > this.heap[largest]) {
largest = leftChildIndex;
}
if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] > this.heap[largest]) {
largest = rightChildIndex;
}
if (largest !== index) {
[this.heap[index], this.heap[largest]] = [this.heap[largest], this.heap[index]];
this.bubbleDown(largest);
}
}
}
Explanation: The “MaxHeap” class implements a heap data structure where the maximum element is always at the root, allowing efficient extraction of the largest value.