40 Advanced JavaScript Coding Exercises Apply your Knowledge

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.