40 Advanced JavaScript Coding Exercises Apply Your Knowledge
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.