diff --git a/src/main/java/com/thealgorithms/recursion/CombinationSum.java b/src/main/java/com/thealgorithms/recursion/CombinationSum.java
new file mode 100644
index 000000000000..caeb3cf70d25
--- /dev/null
+++ b/src/main/java/com/thealgorithms/recursion/CombinationSum.java
@@ -0,0 +1,60 @@
+package com.thealgorithms.recursion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class implements the Combination Sum algorithm using recursion and backtracking.
+ * Given an array of distinct integers candidates and a target integer target,
+ * return a list of all unique combinations of candidates where the chosen numbers sum to target.
+ * The same number may be chosen from candidates an unlimited number of times.
+ *
+ * @see Subset Sum Problem (Wikipedia)
+ * @see Combination Sum (LeetCode)
+ * @author Tejas Rahane
+ */
+public final class CombinationSum {
+ private CombinationSum() {
+ }
+
+ /**
+ * Finds all unique combinations that sum to target.
+ *
+ * @param candidates Array of distinct integers
+ * @param target Target sum
+ * @return List of all unique combinations that sum to target
+ */
+ public static List> combinationSum(int[] candidates, int target) {
+ List> result = new ArrayList<>();
+ if (candidates == null || candidates.length == 0) {
+ return result;
+ }
+ backtrack(candidates, target, 0, new ArrayList<>(), result);
+ return result;
+ }
+
+ /**
+ * Backtracking helper method to find all combinations.
+ *
+ * @param candidates Array of distinct integers
+ * @param target Remaining target sum
+ * @param start Starting index for candidates
+ * @param current Current combination being built
+ * @param result List to store all valid combinations
+ */
+ private static void backtrack(int[] candidates, int target, int start, List current, List> result) {
+ if (target == 0) {
+ result.add(new ArrayList<>(current));
+ return;
+ }
+ if (target < 0) {
+ return;
+ }
+
+ for (int i = start; i < candidates.length; i++) {
+ current.add(candidates[i]);
+ backtrack(candidates, target - candidates[i], i, current, result);
+ current.remove(current.size() - 1);
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
new file mode 100644
index 000000000000..337c6431a6bf
--- /dev/null
+++ b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
@@ -0,0 +1,127 @@
+package com.thealgorithms.recursion;
+import static org.junit.jupiter.api.Assertions.*;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+/**
+ * Comprehensive test class for CombinationSum algorithm
+ * Tests various scenarios including edge cases
+ */
+class CombinationSumTest {
+ @Test
+ void testBasicCase() {
+ int[] candidates = {2, 3, 6, 7};
+ int target = 7;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertTrue(result.contains(Arrays.asList(2, 2, 3)));
+ assertTrue(result.contains(Arrays.asList(7)));
+ assertEquals(2, result.size());
+ }
+ @Test
+ void testMultipleCombinations() {
+ int[] candidates = {2, 3, 5};
+ int target = 8;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertTrue(result.contains(Arrays.asList(2, 2, 2, 2)));
+ assertTrue(result.contains(Arrays.asList(2, 3, 3)));
+ assertTrue(result.contains(Arrays.asList(3, 5)));
+ assertEquals(3, result.size());
+ }
+ @Test
+ void testNoSolution() {
+ int[] candidates = {2};
+ int target = 1;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertTrue(result.isEmpty());
+ }
+ @Test
+ void testSingleElement() {
+
+ int[] candidates = {1};
+ int target = 1;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertEquals(1, result.size());
+ assertTrue(result.contains(Arrays.asList(1)));
+ }
+ @Test
+ void testSingleElementRepeated() {
+
+ int[] candidates = {2};
+ int target = 8;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertEquals(1, result.size());
+ assertTrue(result.contains(Arrays.asList(2, 2, 2, 2)));
+ }
+ @Test
+ void testLargerNumbers() {
+
+ int[] candidates = {10, 1, 2, 7, 6, 1, 5};
+ int target = 8;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertFalse(result.isEmpty());
+ // Verify all combinations sum to target
+ for (List combination : result) {
+ int sum = combination.stream().mapToInt(Integer::intValue).sum();
+ assertEquals(target, sum);
+ }
+ }
+ @Test
+ void testTargetZero() {
+
+ int[] candidates = {1, 2, 3};
+ int target = 0;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ // Should return empty list in the combination
+ assertEquals(1, result.size());
+ assertTrue(result.get(0).isEmpty());
+ }
+ @Test
+ void testEmptyCandidates() {
+
+ int[] candidates = {};
+ int target = 5;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertTrue(result.isEmpty());
+ }
+ @Test
+ void testLargeTarget() {
+ int[] candidates = {3, 5, 8};
+ int target = 11;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ assertTrue(result.contains(Arrays.asList(3, 3, 5)));
+ assertTrue(result.contains(Arrays.asList(3, 8)));
+
+ // Verify all combinations sum to target
+ for (List combination : result) {
+ int sum = combination.stream().mapToInt(Integer::intValue).sum();
+ assertEquals(target, sum);
+ }
+ }
+ @Test
+ void testAllCombinationsValid() {
+ int[] candidates = {2, 3, 6, 7};
+ int target = 7;
+ List> result = CombinationSum.combinationSum(candidates, target);
+
+ // Verify each combination sums to target
+ for (List combination : result) {
+ int sum = 0;
+ for (int num : combination) {
+ sum += num;
+ }
+ assertEquals(target, sum, "Each combination should sum to target");
+ }
+
+ // Verify no duplicates in result
+ assertEquals(result.size(), result.stream().distinct().count());
+ }
+}