diff --git a/Backtracking/AllCombinationsOfSizeK.js b/Backtracking/AllCombinationsOfSizeK.js
index 264acb1f19..bbe4e7fd5b 100644
--- a/Backtracking/AllCombinationsOfSizeK.js
+++ b/Backtracking/AllCombinationsOfSizeK.js
@@ -1,46 +1,28 @@
-/*
-  Problem: Given two numbers, n and k, make all unique combinations of k numbers from 1 to n and in sorted order
-
-  What is combinations?
-  - Combinations is selecting items from a collections without considering the order of selection
-
-  Example:
-  - We have an apple, a banana, and a jackfruit
-  - We have three objects, and need to choose two items, then combinations will be
-
-  1. Apple & Banana
-  2. Apple & Jackfruit
-  3. Banana & Jackfruit
-
-  To read more about combinations, you can visit the following link:
-  - https://betterexplained.com/articles/easy-permutations-and-combinations/
-
-  Solution:
-  - We will be using backtracking to solve this questions
-  - Take one element, and make all them combinations for k-1 elements
-  - Once we get all combinations of that element, pop it and do same for next element
-*/
-
-class Combinations {
-  constructor(n, k) {
-    this.n = n
-    this.k = k
-    this.current = [] // will be used for storing current combination
-    this.combinations = []
-  }
-
-  findCombinations(high = this.n, total = this.k, low = 1) {
-    if (total === 0) {
-      this.combinations.push([...this.current])
-      return this.combinations
+function generateCombinations(n, k) {
+  let currentCombination = []
+  let allCombinations = [] // will be used for storing all combinations
+  let currentValue = 1
+
+  function findCombinations() {
+    if (currentCombination.length === k) {
+      // Add the array of size k to the allCombinations array
+      allCombinations.push([...currentCombination])
+      return
     }
-    for (let i = low; i <= high; i++) {
-      this.current.push(i)
-      this.findCombinations(high, total - 1, i + 1)
-      this.current.pop()
+    if (currentValue > n) {
+      // Check for exceeding the range
+      return
     }
-    return this.combinations
+    currentCombination.push(currentValue++)
+    findCombinations()
+    currentCombination.pop()
+    findCombinations()
+    currentValue--
   }
+
+  findCombinations()
+
+  return allCombinations
 }
 
-export { Combinations }
+export { generateCombinations }
diff --git a/Backtracking/tests/AllCombinationsOfSizeK.test.js b/Backtracking/tests/AllCombinationsOfSizeK.test.js
index a2135e54bf..29b656a2c4 100644
--- a/Backtracking/tests/AllCombinationsOfSizeK.test.js
+++ b/Backtracking/tests/AllCombinationsOfSizeK.test.js
@@ -1,9 +1,9 @@
-import { Combinations } from '../AllCombinationsOfSizeK'
+import { generateCombinations } from '../AllCombinationsOfSizeK'
 
 describe('AllCombinationsOfSizeK', () => {
   it('should return 3x2 matrix solution for n = 3 and k = 2', () => {
-    const test1 = new Combinations(3, 2)
-    expect(test1.findCombinations()).toEqual([
+    const res = generateCombinations(3, 2)
+    expect(res).toEqual([
       [1, 2],
       [1, 3],
       [2, 3]
@@ -11,8 +11,8 @@ describe('AllCombinationsOfSizeK', () => {
   })
 
   it('should return 6x2 matrix solution for n = 4 and k = 2', () => {
-    const test2 = new Combinations(4, 2)
-    expect(test2.findCombinations()).toEqual([
+    const res = generateCombinations(4, 2)
+    expect(res).toEqual([
       [1, 2],
       [1, 3],
       [1, 4],
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 730df9cdf9..1b2c40b3c8 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -311,6 +311,7 @@
   * [LinearSearch](Search/LinearSearch.js)
   * [Minesweeper](Search/Minesweeper.js)
   * [QuickSelectSearch](Search/QuickSelectSearch.js)
+  * [RabinKarp](Search/RabinKarp.js)
   * [SlidingWindow](Search/SlidingWindow.js)
   * [StringSearch](Search/StringSearch.js)
   * [TernarySearch](Search/TernarySearch.js)
diff --git a/Dynamic-Programming/ZeroOneKnapsack.js b/Dynamic-Programming/ZeroOneKnapsack.js
index 3913d016b3..52a06aa130 100644
--- a/Dynamic-Programming/ZeroOneKnapsack.js
+++ b/Dynamic-Programming/ZeroOneKnapsack.js
@@ -1,26 +1,34 @@
 /**
  * A Dynamic Programming based solution for calculating Zero One Knapsack
  * https://en.wikipedia.org/wiki/Knapsack_problem
+ *
+ * Time and Space Complexity: O(n*cap)
  */
-
 const zeroOneKnapsack = (arr, n, cap, cache) => {
+  // Base Case: No capacity or no items
   if (cap === 0 || n === 0) {
     cache[n][cap] = 0
     return cache[n][cap]
   }
+
+  // Lookup (value already calculated)
   if (cache[n][cap] !== -1) {
     return cache[n][cap]
   }
+
+  // Profit when excluding the nth item
+  let notPick = zeroOneKnapsack(arr, n - 1, cap, cache)
+
+  // Profit when including the nth item
+  let pick = 0
   if (arr[n - 1][0] <= cap) {
-    cache[n][cap] = Math.max(
-      arr[n - 1][1] + zeroOneKnapsack(arr, n - 1, cap - arr[n - 1][0], cache),
-      zeroOneKnapsack(arr, n - 1, cap, cache)
-    )
-    return cache[n][cap]
-  } else {
-    cache[n][cap] = zeroOneKnapsack(arr, n - 1, cap, cache)
-    return cache[n][cap]
+    // If weight of the nth item is within the capacity
+    pick =
+      arr[n - 1][1] + zeroOneKnapsack(arr, n - 1, cap - arr[n - 1][0], cache)
   }
+
+  cache[n][cap] = Math.max(pick, notPick) // maximize profit
+  return cache[n][cap]
 }
 
 const example = () => {
diff --git a/Hashes/MD5.js b/Hashes/MD5.js
new file mode 100644
index 0000000000..42bef45cc5
--- /dev/null
+++ b/Hashes/MD5.js
@@ -0,0 +1,205 @@
+// Module that replicates the MD5 Cryptographic Hash
+// function in Javascript.
+
+// main variables
+const S = [
+  7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5,
+  9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11,
+  16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15,
+  21
+]
+
+const K = [
+  0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
+  0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+  0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
+  0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+  0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
+  0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+  0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
+  0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+  0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
+  0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+  0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+]
+
+/**
+ * Separates an array into equal sized chunks
+ *
+ * @param {Array|string} array - array or string to separate into chunks
+ * @param {number} size - number of elements wanted in each chunk
+ * @return {Array} - array of original array split into chunks
+ *
+ * @example
+ *      chunkify("this is a test", 2)
+ */
+function chunkify(array, size) {
+  const chunks = []
+  for (let i = 0; i < array.length; i += size) {
+    chunks.push(array.slice(i, i + size))
+  }
+  return chunks
+}
+
+/**
+ * Rotates the bits to the left
+ *
+ * @param {number} bits - 32 bit number
+ * @param {number} turns - number of rotations to make
+ * @return {number} - number after bits rotation
+ *
+ * @example
+ *      rotateLeft(0b1011, 3); // 0b1011000
+ */
+function rotateLeft(bits, turns) {
+  return (bits << turns) | (bits >>> (32 - turns))
+}
+
+/**
+ * Converts Uint8Array to Uint32Array
+ *
+ * @param {Uint8Array} u8Array Uint8Array to convert
+ * @returns {Uint32Array} - Required Uint32Array
+ */
+function u8ToU32(u8Array) {
+  const uint32Array = new Uint32Array(u8Array.length / 4)
+
+  for (let i = 0; i < u8Array.length; i += 4) {
+    uint32Array[i / 4] =
+      (u8Array[i] |
+        (u8Array[i + 1] << 8) |
+        (u8Array[i + 2] << 16) |
+        (u8Array[i + 3] << 24)) >>>
+      0
+  }
+
+  return uint32Array
+}
+
+/**
+ * Converts Uint32Array to Uint8Array
+ *
+ * @param {Uint32Array} u32Array Uint32Array to convert
+ * @returns {Uint8Array} - Required Uint8Array
+ */
+function u32ToU8(u32Array) {
+  const uint8Array = new Uint8Array(u32Array.length * 4)
+
+  for (let i = 0; i < u32Array.length; i++) {
+    uint8Array[i * 4] = u32Array[i] & 0xff
+    uint8Array[i * 4 + 1] = (u32Array[i] >> 8) & 0xff
+    uint8Array[i * 4 + 2] = (u32Array[i] >> 16) & 0xff
+    uint8Array[i * 4 + 3] = (u32Array[i] >> 24) & 0xff
+  }
+
+  return uint8Array
+}
+
+/**
+ * Adds padding to the end of the given array
+ *
+ * @param {Uint8Array} u8Array Array to pad
+ * @param {number} size Resulting size of the array
+ */
+function padEnd(u8Array, size) {
+  const result = new Uint8Array(size)
+  result.set(u8Array)
+  result.fill(0, u8Array.length)
+
+  return result
+}
+
+/**
+ * Pre-processes message to feed the algorithm loop
+ *
+ * @param {Uint8Array} message - message to pre-process
+ * @return {Uint32Array} - processed message
+ */
+function preProcess(message) {
+  // Extend message by adding '0'
+  //
+  // message.length + 1 is for adding '1' bit
+  // 56 - (length % 64) is for padding with '0's
+  // 8 is for appending 64 bit message length
+  let m = padEnd(
+    message,
+    message.length + 1 + (56 - ((message.length + 1) % 64)) + 8
+  )
+
+  // Add '1' bit at the end of the message
+  m[message.length] = 1 << 7
+
+  // convert message to 32 bit uint array
+  m = u8ToU32(m)
+
+  // Append the length of the message to the end
+  // (ml / 0x100000000) | 0 is equivalent to (ml >> 32) & 0xffffffff) in other languages
+  let ml = message.length * 8
+  m[m.length - 2] = ml & 0xffffffff
+  m[m.length - 1] = (ml / 0x100000000) | 0
+
+  return m
+}
+
+/**
+ * Hashes message using MD5 Cryptographic Hash Function
+ *
+ * @see
+ * For more info: https://en.wikipedia.org/wiki/MD5
+ *
+ * @param {Uint8Array} message - message to hash
+ * @return {Uint8Array} - message digest (hash value)
+ */
+function MD5(message) {
+  // Initialize variables:
+  let [a0, b0, c0, d0] = [
+    0x67452301 >>> 0,
+    0xefcdab89 >>> 0,
+    0x98badcfe >>> 0,
+    0x10325476 >>> 0
+  ]
+
+  // pre-process message and split into 512 bit chunks
+  const words = Array.from(preProcess(message))
+  const chunks = chunkify(words, 16)
+
+  chunks.forEach(function (chunk, _) {
+    // initialize variables for this chunk
+    let [A, B, C, D] = [a0, b0, c0, d0]
+
+    for (let i = 0; i < 64; i++) {
+      let [F, g] = [0, 0]
+
+      if (i <= 15) {
+        F = (B & C) | (~B & D)
+        g = i
+      } else if (i <= 31) {
+        F = (D & B) | (~D & C)
+        g = (5 * i + 1) % 16
+      } else if (i <= 47) {
+        F = B ^ C ^ D
+        g = (3 * i + 5) % 16
+      } else {
+        F = C ^ (B | ~D)
+        g = (7 * i) % 16
+      }
+
+      F = (F + A + K[i] + chunk[g]) >>> 0
+      A = D
+      D = C
+      C = B
+      B = ((B + rotateLeft(F, S[i])) & 0xffffffff) >>> 0
+    }
+
+    // add values for this chunk to main hash variables (unsigned)
+    a0 = (a0 + A) >>> 0
+    b0 = (b0 + B) >>> 0
+    c0 = (c0 + C) >>> 0
+    d0 = (d0 + D) >>> 0
+  })
+
+  return u32ToU8([a0, b0, c0, d0])
+}
+
+// export MD5 function
+export { MD5 }
diff --git a/Hashes/tests/MD5.test.js b/Hashes/tests/MD5.test.js
new file mode 100644
index 0000000000..5c44c7a57c
--- /dev/null
+++ b/Hashes/tests/MD5.test.js
@@ -0,0 +1,38 @@
+import { MD5 } from '../MD5'
+
+/**
+ * Returns the MD5 hash of the given message as a hexadecimal string
+ *
+ * @param {Uint8Array} message - message to hash
+ * @return {string} - hash as a hexadecimal string
+ */
+function hexMD5(message) {
+  return Array.from(MD5(message), (byte) =>
+    byte.toString(16).padStart(2, '0')
+  ).join('')
+}
+
+describe('Testing MD5 function', () => {
+  it('should return the correct hash for "The quick brown fox jumps over the lazy dog"', () => {
+    const input = new TextEncoder().encode(
+      'The quick brown fox jumps over the lazy dog'
+    )
+    const hash = hexMD5(input)
+
+    expect(hash).toBe('9e107d9d372bb6826bd81d3542a419d6')
+  })
+
+  it('should return the correct hash for "JavaScript!"', () => {
+    const input = new TextEncoder().encode('JavaScript!')
+    const hash = hexMD5(input)
+
+    expect(hash).toBe('209eddd6b61af0643907a8e069a08fb8')
+  })
+
+  it('should correctly hash an empty string', () => {
+    const input = new TextEncoder().encode('')
+    const hash = hexMD5(input)
+
+    expect(hash).toBe('d41d8cd98f00b204e9800998ecf8427e')
+  })
+})
diff --git a/Project-Euler/Problem001.js b/Project-Euler/Problem001.js
index c47c10bd1c..a88f912603 100644
--- a/Project-Euler/Problem001.js
+++ b/Project-Euler/Problem001.js
@@ -1,20 +1,23 @@
 // https://projecteuler.net/problem=1
-/*    Multiples of 3 and 5
-    If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
+/* Multiples of 3 and 5
+If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6, and 9. The sum of these multiples is 23.
 Find the sum of all the multiples of 3 or 5 below the provided parameter value number.
 */
 
+// This method uses the nSum function to add the nSum for 3 and 5. However, it needs to subtract the nSum for 15 once to avoid double counting.
 const multiplesThreeAndFive = (num) => {
   if (num < 1) throw new Error('No natural numbers exist below 1')
+  num -= 1
+  let sum = 0
 
-  let total = 0
-  // total for calculating the sum
-  for (let i = 1; i < num; i++) {
-    if (i % 3 === 0 || i % 5 === 0) {
-      total += i
-    }
-  }
-  return total
+  // The nSum function calculates the sum of the first n numbers in the sequence with a common difference of num.
+  // Here, n is denoted as frequency.
+  const nSum = (num, frequency) => (frequency * (frequency + 1) * num) >> 1
+
+  sum += nSum(3, Math.floor(num / 3))
+  sum += nSum(5, Math.floor(num / 5))
+  sum -= nSum(15, Math.floor(num / 15))
+  return sum
 }
 
 export { multiplesThreeAndFive }
diff --git a/Search/RabinKarp.js b/Search/RabinKarp.js
new file mode 100644
index 0000000000..e6d6394ede
--- /dev/null
+++ b/Search/RabinKarp.js
@@ -0,0 +1,64 @@
+/*
+ * Implements the Rabin-Karp algorithm for pattern searching.
+ *
+ * The Rabin-Karp algorithm is a string searching algorithm that uses hashing to find patterns in strings.
+ * It is faster than naive string matching algorithms because it avoids comparing every character in the text.
+ *
+ * This implementation uses a rolling hash function to efficiently compute the hash values of substrings.
+ * It also uses a modulo operator to reduce the size of the hash values, which helps to prevent hash collisions.
+ *
+ * The algorithm returns an array of indices where the pattern is found in the text. If the pattern is not
+ * found, the algorithm returns an empty array.
+ *
+ * [Reference](https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm)
+ */
+
+const BASE = 256 // The number of characters in the alphabet
+const MOD = 997 // A prime number used for the hash function
+
+function rabinKarpSearch(text, pattern) {
+  const patternLength = pattern.length
+  const textLength = text.length
+  const hashPattern = hash(pattern, patternLength)
+  const hashText = []
+  const indices = []
+
+  // Calculate the hash of the first substring in the text
+  hashText[0] = hash(text, patternLength)
+
+  // Precompute BASE^(patternLength-1) % MOD
+  const basePow = Math.pow(BASE, patternLength - 1) % MOD
+
+  for (let i = 1; i <= textLength - patternLength + 1; i++) {
+    // Update the rolling hash by removing the first character
+    // and adding the next character in the text
+    hashText[i] =
+      (BASE * (hashText[i - 1] - text.charCodeAt(i - 1) * basePow) +
+        text.charCodeAt(i + patternLength - 1)) %
+      MOD
+
+    // In case of hash collision, check character by character
+    if (hashText[i] < 0) {
+      hashText[i] += MOD
+    }
+
+    // Check if the hashes match and perform a character-wise comparison
+    if (hashText[i] === hashPattern) {
+      if (text.substring(i, i + patternLength) === pattern) {
+        indices.push(i) // Store the index where the pattern is found
+      }
+    }
+  }
+
+  return indices
+}
+
+function hash(str, length) {
+  let hashValue = 0
+  for (let i = 0; i < length; i++) {
+    hashValue = (hashValue * BASE + str.charCodeAt(i)) % MOD
+  }
+  return hashValue
+}
+
+export { rabinKarpSearch }
diff --git a/Search/test/RabinKarp.test.js b/Search/test/RabinKarp.test.js
new file mode 100644
index 0000000000..3d10097509
--- /dev/null
+++ b/Search/test/RabinKarp.test.js
@@ -0,0 +1,30 @@
+import { rabinKarpSearch } from '../RabinKarp'
+
+describe('Rabin-Karp Search', function () {
+  it('should find the pattern in the text', function () {
+    const text = 'ABABDABACDABABCABAB'
+    const pattern = 'DAB'
+    const expected = [4, 9]
+
+    const result = rabinKarpSearch(text, pattern)
+    expect(result).to.deep.equal(expected)
+  })
+
+  it('should handle multiple occurrences of the pattern', function () {
+    const text = 'ABABABABABAB'
+    const pattern = 'ABAB'
+    const expected = [2, 4, 6, 8]
+
+    const result = rabinKarpSearch(text, pattern)
+    expect(result).to.deep.equal(expected)
+  })
+
+  it('should handle pattern not found', function () {
+    const text = 'ABCD'
+    const pattern = 'XYZ'
+    const expected = []
+
+    const result = rabinKarpSearch(text, pattern)
+    expect(result).to.deep.equal(expected)
+  })
+})
