Divide and Conquer: Code Templates in 6 Languages
If you have not read the concept guide yet, start there for the intuition and complexity analysis. This page gives you the code you can memorize and adapt during an interview. Every template follows the same skeleton: split the input, solve the pieces recursively, and combine the results.
Main Template: Merge Sort
Merge sort is the most common divide and conquer implementation interviewers ask you to write. It splits the array into halves, sorts each half, and merges the sorted halves back together. The merge step is what makes the whole thing work, so pay attention to it.
Use this for Sort an Array .
function mergeSort(arr, left = 0, right = arr.length - 1) {
if (left >= right) return; // base case: range is empty or one element
const mid = Math.floor((left + right) / 2);
// Split: both halves must stay disjoint and cover the full range
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Combine: the halves are sorted, now merge them
merge(arr, left, mid, right);
}
function merge(arr, left, mid, right) {
// Copy both halves so the merge can write over the original
// range without losing values it still has to compare
const n1 = mid - left + 1;
const n2 = right - mid;
const leftArr = new Array(n1);
const rightArr = new Array(n2);
for (let i = 0; i < n1; i++) leftArr[i] = arr[left + i];
for (let j = 0; j < n2; j++) rightArr[j] = arr[mid + 1 + j];
let i = 0, j = 0, k = left;
// Place the smaller current head of each half back into the array
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) arr[k++] = leftArr[i++];
else arr[k++] = rightArr[j++];
}
// One half runs out first; the rest of the other is already in order
while (i < n1) arr[k++] = leftArr[i++];
while (j < n2) arr[k++] = rightArr[j++];
}Code Breakdown
Key Variables
leftandright: the range of the array the current call owns. Every recursive call narrows this range until it hits the base case.mid: the boundary between the two halves, computed as the midpoint of left and right. Everything at or before mid belongs to the first half.leftArrandrightArr(merge): temporary copies of the two sorted halves, needed so the merge can write over the original range without losing values it still has to read.i,j, andk(merge): the read cursors for the left and right copies plus the write cursor for the original array. i and j advance when their element is placed, k advances on every write.
Visual Mechanism
stateDiagram-v2
[*] --> Split: sort both halves recursively
Split --> Merge: both halves sorted
Merge --> Done: write sorted merge back
Done --> [*]
Critical Sections
The base case left >= right stops the recursion when the range has one element or fewer, and it is what prevents infinite recursion on empty input.
The split is just arithmetic. Because left and mid are inclusive and the second half starts at mid plus one, every element lands in exactly one half, which is why the off-by-one at this line matters.
The merge loop compares the current heads of both halves and writes the smaller one back into the original array. The two trailing loops exist because after one half is exhausted, the remainder of the other half is already in order and can be copied over directly.
The temporary arrays are not an optimization detail. Without them, writing the smaller element back would overwrite values the merge still needs to compare.
Variations
1. Quick Sort (In-Place Partition)
Quick sort sorts in place instead of merging. It partitions the segment around a pivot so that smaller elements land on the left and larger ones on the right, then recurses on both sides. The partition function below also powers quickselect in the next variation.
function quickSort(arr, low = 0, high = arr.length - 1) {
if (low >= high) return;
const pivotIndex = partition(arr, low, high);
// The pivot is already in its final position, so recurse
// on the sides around it
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
function partition(arr, low, high) {
const pivot = arr[high]; // rightmost element as the pivot
let i = low;
for (let j = low; j < high; j++) {
if (arr[j] < pivot) {
// Move elements smaller than the pivot left,
// everything else is pushed right implicitly
[arr[i], arr[j]] = [arr[j], arr[i]];
i++;
}
}
// The pivot lands between the two groups
[arr[i], arr[high]] = [arr[high], arr[i]];
return i;
}Visual
graph TD
A["Array segment"] --> B["Pick pivot (last element)"]
B --> C["Swap smaller elements left"]
C --> D["Pivot to its final position"]
D --> E["Recurse on both sides"]
2. Quickselect (Kth Largest Element)
Quickselect borrows the partition step but recurses into only one side, the side that contains the target index. That pruning is what drops the average cost to linear. For the kth largest element, the target index in sorted order is the array length minus k.
Use this for Kth Largest Element in an Array .
function findKthLargest(nums, k) {
// The kth largest value sits at index n-k once the array is sorted
const target = nums.length - k;
let low = 0, high = nums.length - 1;
while (low <= high) {
const pivotIndex = partition(nums, low, high);
// Follow only the branch that contains the target index,
// which is why quickselect averages O(N) instead of O(N log N)
if (pivotIndex === target) return nums[pivotIndex];
if (pivotIndex < target) low = pivotIndex + 1;
else high = pivotIndex - 1;
}
}
function partition(nums, low, high) {
const pivot = nums[high];
let i = low;
for (let j = low; j < high; j++) {
if (nums[j] < pivot) {
[nums[i], nums[j]] = [nums[j], nums[i]];
i++;
}
}
[nums[i], nums[high]] = [nums[high], nums[i]];
return i;
}Visual
graph TD
A["Partition around pivot"] --> B{"Pivot index == target?"}
B -->|Yes| C["Return pivot value"]
B -->|No| D{"Pivot index < target?"}
D -->|Yes| E["Search right half"]
D -->|No| F["Search left half"]
E --> A
F --> A
Now head to the practice problems to apply these templates to real interview questions.