Common Pitfalls and Edge Cases
1. Integer Overflow Handling
| Issue | Example | Detection | Solution |
|---|---|---|---|
| Addition Overflow | INT_MAX + 1 = INT_MIN |
Check if a + b > MAX before adding |
if (a > MAX - b) overflow |
| Multiplication Overflow | a * b exceeds MAX |
Check if a > MAX / b |
Use BigInt or check bounds first |
| Subtraction Underflow | INT_MIN - 1 = INT_MAX |
Check if a - b < MIN |
if (a < MIN + b) underflow |
| JavaScript Safety | Number.MAX_SAFE_INTEGER = 2^53 - 1 | Beyond this, precision lost | Use BigInt for large numbers |
Example: Detecting overflow before operation
// JavaScript number limits
const MAX_SAFE = Number.MAX_SAFE_INTEGER; // 2^53 - 1
const MIN_SAFE = Number.MIN_SAFE_INTEGER; // -(2^53 - 1)
// Check addition overflow
function safeAdd(a, b) {
if (a > 0 && b > 0 && a > MAX_SAFE - b) {
throw new Error('Addition overflow');
}
if (a < 0 && b < 0 && a < MIN_SAFE - b) {
throw new Error('Addition underflow');
}
return a + b;
}
// Check multiplication overflow
function safeMultiply(a, b) {
if (a === 0 || b === 0) return 0;
if (a > 0 && b > 0 && a > Math.floor(MAX_SAFE / b)) {
throw new Error('Multiplication overflow');
}
if (a < 0 && b < 0 && a < Math.ceil(MAX_SAFE / b)) {
throw new Error('Multiplication overflow');
}
if ((a > 0 && b < 0) || (a < 0 && b > 0)) {
if (Math.abs(a) > Math.floor(MAX_SAFE / Math.abs(b))) {
throw new Error('Multiplication underflow');
}
}
return a * b;
}
// Using BigInt for guaranteed accuracy
function bigIntAdd(a, b) {
return BigInt(a) + BigInt(b);
}
// Example:
console.log(MAX_SAFE + 1); // 9007199254740992 (wrong!)
console.log(MAX_SAFE + 2); // 9007199254740992 (lost precision)
console.log(BigInt(MAX_SAFE) + 2n); // 9007199254740993n (correct)
Example: Common overflow scenarios
// Scenario 1: Sum of array (can overflow)
function sumArray(arr) {
let sum = 0;
for (const num of arr) {
// Check before adding
if (num > 0 && sum > MAX_SAFE - num) {
console.warn('Potential overflow, switching to BigInt');
return arr.reduce((a, b) => BigInt(a) + BigInt(b), 0n);
}
sum += num;
}
return sum;
}
// Scenario 2: Factorial (overflows quickly)
function factorial(n) {
let result = 1n; // Use BigInt from start
for (let i = 2n; i <= n; i++) {
result *= i;
}
return result;
}
// factorial(20) = 2432902008176640000 (within safe range)
// factorial(21) = 51090942171709440000 (exceeds MAX_SAFE)
// Scenario 3: Power calculation
function safePower(base, exp) {
if (exp === 0) return 1;
let result = 1;
for (let i = 0; i < exp; i++) {
if (result > MAX_SAFE / base) {
// Switch to BigInt
return BigInt(base) ** BigInt(exp);
}
result *= base;
}
return result;
}
// Scenario 4: Binary search mid calculation
// WRONG: mid = (left + right) / 2 (can overflow)
// RIGHT: mid = left + Math.floor((right - left) / 2)
function binarySearchMid(left, right) {
// Prevents overflow
return left + Math.floor((right - left) / 2);
}
2. Floating Point Precision Errors
| Issue | Example | Problem | Solution |
|---|---|---|---|
| Decimal Representation | 0.1 + 0.2 = 0.30000000000000004 |
Binary can't represent 0.1 exactly | Use epsilon comparison |
| Equality Comparison | 0.1 + 0.2 === 0.3 // false |
Direct comparison fails | Math.abs(a - b) < EPSILON |
| Accumulation Error | Sum of many small floats loses precision | Rounding errors compound | Use integer cents, not float dollars |
| Division by Zero | 1 / 0 = Infinity |
Not an error in JavaScript | Check denominator before dividing |
Example: Floating point comparison
// Machine epsilon for double precision
const EPSILON = Number.EPSILON; // 2^-52 ≈ 2.22e-16
// Safe equality comparison
function almostEqual(a, b, epsilon = EPSILON) {
return Math.abs(a - b) < epsilon;
}
// Examples of precision issues:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
console.log(almostEqual(0.1 + 0.2, 0.3)); // true
// Larger epsilon for practical comparisons
const TOLERANCE = 1e-10;
function closeEnough(a, b) {
return Math.abs(a - b) < TOLERANCE;
}
// Relative comparison (for different magnitudes)
function relativeEqual(a, b, tolerance = 1e-9) {
const diff = Math.abs(a - b);
const largest = Math.max(Math.abs(a), Math.abs(b));
if (largest === 0) return diff < tolerance;
return diff / largest < tolerance;
}
// Examples:
console.log(relativeEqual(1000.0001, 1000.0002)); // true
console.log(relativeEqual(0.0001, 0.0002)); // false
// Better approach for money: use integers (cents)
function addMoney(dollars1, dollars2) {
const cents1 = Math.round(dollars1 * 100);
const cents2 = Math.round(dollars2 * 100);
return (cents1 + cents2) / 100;
}
console.log(addMoney(0.1, 0.2)); // 0.3 (exact)
Example: Common floating point pitfalls
// Pitfall 1: Loop counter with floats
// WRONG:
for (let i = 0.0; i !== 1.0; i += 0.1) {
// Infinite loop! i never exactly equals 1.0
}
// RIGHT:
for (let i = 0; i < 10; i++) {
const x = i * 0.1;
// Use x
}
// Pitfall 2: Accumulation error
function sumWrong(arr) {
let sum = 0.0;
for (const val of arr) {
sum += val; // Error accumulates
}
return sum;
}
// Better: Kahan summation algorithm
function kahanSum(arr) {
let sum = 0.0;
let compensation = 0.0;
for (const val of arr) {
const y = val - compensation;
const t = sum + y;
compensation = (t - sum) - y;
sum = t;
}
return sum;
}
// Pitfall 3: Division issues
function safeDivide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
if (!isFinite(b) || !isFinite(a)) {
throw new Error('Invalid operands');
}
return a / b;
}
// Check for special values
function isValidNumber(x) {
return typeof x === 'number' &&
isFinite(x) &&
!isNaN(x);
}
// Pitfall 4: Modulo with floats
console.log(5.5 % 2.2); // 1.0999999999999996 (not 1.1)
// Better: convert to integers
function floatMod(a, b, precision = 10) {
const scale = Math.pow(10, precision);
const aScaled = Math.round(a * scale);
const bScaled = Math.round(b * scale);
return (aScaled % bScaled) / scale;
}
3. Array Index Out of Bounds
| Issue | JavaScript Behavior | Common Cause | Prevention |
|---|---|---|---|
| Negative Index | Returns undefined |
Loop starts at -1, pointer arithmetic | Check i >= 0 |
| Index = Length | Returns undefined |
Using <= instead of < |
Check i < arr.length |
| Empty Array Access | arr[0] returns undefined |
Not checking if array is empty | Check arr.length > 0 first |
| Dynamic Length | Length changes during iteration | Modifying array while iterating | Cache length or iterate backwards |
Example: Array bounds checking patterns
// Safe array access
function safeGet(arr, index, defaultValue = null) {
if (index < 0 || index >= arr.length) {
return defaultValue;
}
return arr[index];
}
// Common pitfall: off-by-one in loop
// WRONG:
for (let i = 0; i <= arr.length; i++) {
console.log(arr[i]); // undefined at arr[arr.length]
}
// RIGHT:
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// Accessing neighbors in 2D array
function getNeighbors(matrix, row, col) {
const neighbors = [];
const directions = [[-1,0], [1,0], [0,-1], [0,1]];
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
// Bounds check before access
if (newRow >= 0 && newRow < matrix.length &&
newCol >= 0 && newCol < matrix[0].length) {
neighbors.push(matrix[newRow][newCol]);
}
}
return neighbors;
}
// Sliding window: check both ends
function slidingWindow(arr, k) {
if (arr.length < k) return [];
const result = [];
for (let i = 0; i <= arr.length - k; i++) {
// i + k - 1 < arr.length guaranteed
const window = arr.slice(i, i + k);
result.push(window);
}
return result;
}
// Binary search: prevent out of bounds
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1; // Not arr.length!
while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
// mid is always valid: left <= mid <= right < arr.length
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// Modifying array while iterating: iterate backwards
function removeEvens(arr) {
// Forward iteration would skip elements
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] % 2 === 0) {
arr.splice(i, 1);
}
}
return arr;
}
4. Null Pointer Safety Checks
| Issue | JavaScript Equivalent | Common Scenario | Solution |
|---|---|---|---|
| Null Dereference | null.property throws TypeError |
Accessing property of null/undefined | Optional chaining obj?.prop |
| Undefined Variables | undefined is not null |
Uninitialized variables, missing properties | Nullish coalescing ?? |
| Empty Object/Array | {} and [] are truthy |
Checking existence vs emptiness | Check length or Object.keys |
| Function Return | May return null/undefined unexpectedly | find(), getElementById(), etc. | Always validate return values |
Example: Null/undefined safety patterns
// Modern JavaScript: optional chaining
function getNestedValue(obj) {
// WRONG: crashes if obj is null or any property missing
// return obj.user.address.city;
// RIGHT: safe navigation
return obj?.user?.address?.city;
}
// Nullish coalescing: default values
function getUserName(user) {
// WRONG: empty string is falsy
// return user.name || 'Anonymous';
// RIGHT: only null/undefined trigger default
return user?.name ?? 'Anonymous';
}
// Linked list: null checks
function traverseList(head) {
let current = head;
const values = [];
while (current !== null && current !== undefined) {
values.push(current.val);
current = current.next;
}
return values;
}
// Shorter: while (current)
function traverseListShort(head) {
const values = [];
let current = head;
while (current) { // null and undefined are falsy
values.push(current.val);
current = current.next;
}
return values;
}
// Tree operations: check before accessing
function getHeight(root) {
if (!root) return 0;
const leftHeight = getHeight(root.left);
const rightHeight = getHeight(root.right);
return 1 + Math.max(leftHeight, rightHeight);
}
// Array methods that return null/undefined
function findElement(arr, target) {
const result = arr.find(x => x === target);
if (result === undefined) {
console.log('Not found');
return null;
}
return result;
}
Example: Defensive programming
// Input validation
function processUser(user) {
// Guard clauses at the top
if (!user) {
throw new Error('User is required');
}
if (typeof user !== 'object') {
throw new Error('User must be an object');
}
if (!user.id) {
throw new Error('User ID is required');
}
// Now safe to process
return {
id: user.id,
name: user.name ?? 'Unknown',
email: user.email ?? '',
role: user.role ?? 'user'
};
}
// Default parameters
function createUser(name = 'Anonymous', age = 0) {
return { name, age };
}
// Checking array/object emptiness
function isEmpty(value) {
if (value === null || value === undefined) return true;
if (Array.isArray(value)) return value.length === 0;
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}
return !value; // For strings, numbers, etc.
}
// Type checking utility
function isValidNode(node) {
return node !== null &&
node !== undefined &&
typeof node === 'object' &&
'val' in node;
}
// Graph traversal with null checks
function bfs(graph, start) {
if (!graph || !start) return [];
const visited = new Set();
const queue = [start];
const result = [];
while (queue.length > 0) {
const node = queue.shift();
if (!node || visited.has(node)) continue;
visited.add(node);
result.push(node.val);
// Safely access neighbors
const neighbors = graph.get(node) ?? [];
for (const neighbor of neighbors) {
if (neighbor && !visited.has(neighbor)) {
queue.push(neighbor);
}
}
}
return result;
}
5. Stack Overflow Recursion Depth
| Issue | Cause | Limit | Solution |
|---|---|---|---|
| Call Stack Limit | Too many nested function calls | ~10,000 in Chrome, varies by browser | Convert to iteration |
| Deep Recursion | No base case or base case never reached | Infinite recursion | Ensure base case is reachable |
| Tail Recursion | Last operation is recursive call | Some engines optimize (not JavaScript) | Use explicit iteration |
| Large Input | Recursion depth proportional to input size | n > 10,000 usually fails | Iterative solution or memoization |
Example: Stack overflow scenarios
// Problematic: deep recursion
function factorialRecursive(n) {
if (n <= 1) return 1;
return n * factorialRecursive(n - 1);
}
// factorialRecursive(100000) -> Stack overflow!
// Solution 1: Iterative version
function factorialIterative(n) {
let result = 1n;
for (let i = 2n; i <= n; i++) {
result *= i;
}
return result;
}
// Problematic: missing base case
function infiniteRecursion(n) {
// Forgot base case!
return infiniteRecursion(n - 1);
}
// Solution: always have base case
function properRecursion(n) {
if (n <= 0) return 0; // Base case
return n + properRecursion(n - 1);
}
// Deep tree traversal
function countNodesRecursive(root) {
if (!root) return 0;
return 1 + countNodesRecursive(root.left) +
countNodesRecursive(root.right);
}
// For very deep trees, use iteration
function countNodesIterative(root) {
if (!root) return 0;
const stack = [root];
let count = 0;
while (stack.length > 0) {
const node = stack.pop();
count++;
if (node.left) stack.push(node.left);
if (node.right) stack.push(node.right);
}
return count;
}
// Fibonacci: exponential recursion
function fibRecursive(n) {
if (n <= 1) return n;
return fibRecursive(n - 1) + fibRecursive(n - 2);
}
// fibRecursive(50) takes forever!
// Solution: memoization
function fibMemo(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
return memo[n];
}
// Or iterative
function fibIterative(n) {
if (n <= 1) return n;
let prev = 0, curr = 1;
for (let i = 2; i <= n; i++) {
[prev, curr] = [curr, prev + curr];
}
return curr;
}
Example: Converting recursion to iteration
// Pattern 1: Single recursive call (tail recursion)
// Recursive:
function sumRecursive(arr, i = 0) {
if (i >= arr.length) return 0;
return arr[i] + sumRecursive(arr, i + 1);
}
// Iterative:
function sumIterative(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// Pattern 2: Multiple recursive calls (tree-like)
// Recursive:
function treePathsRecursive(root, path = []) {
if (!root) return [];
path.push(root.val);
if (!root.left && !root.right) {
return [path.slice()];
}
const paths = [];
if (root.left) {
paths.push(...treePathsRecursive(root.left, path));
}
if (root.right) {
paths.push(...treePathsRecursive(root.right, path));
}
path.pop();
return paths;
}
// Iterative with explicit stack:
function treePathsIterative(root) {
if (!root) return [];
const paths = [];
const stack = [{ node: root, path: [root.val] }];
while (stack.length > 0) {
const { node, path } = stack.pop();
if (!node.left && !node.right) {
paths.push(path);
continue;
}
if (node.left) {
stack.push({
node: node.left,
path: [...path, node.left.val]
});
}
if (node.right) {
stack.push({
node: node.right,
path: [...path, node.right.val]
});
}
}
return paths;
}
// Depth limit approach
function recursionWithLimit(n, depth = 0, maxDepth = 10000) {
if (depth > maxDepth) {
throw new Error('Recursion depth exceeded');
}
if (n <= 0) return 0;
return n + recursionWithLimit(n - 1, depth + 1, maxDepth);
}
Note: JavaScript engines have varying call stack limits (typically 10,000-50,000 frames). When recursion depth is proportional to input size, prefer iterative solutions or use memoization to reduce depth. Always ensure base cases are reachable.
6. Off by One Errors
| Scenario | Wrong | Correct | Common In |
|---|---|---|---|
| Loop Boundary | i <= arr.length |
i < arr.length |
Array iteration |
| Substring Length | s.substring(0, n) has n+1 chars |
s.substring(0, n) has n chars |
String slicing |
| Range Inclusive/Exclusive | Confusing [start, end) vs [start, end] | JavaScript uses [start, end) | slice, substring, etc. |
| Binary Search Initialization | right = arr.length |
right = arr.length - 1 |
Binary search |
Example: Common off-by-one mistakes
// Mistake 1: Array iteration
const arr = [1, 2, 3, 4, 5];
// WRONG: tries to access arr[5] which is undefined
for (let i = 0; i <= arr.length; i++) {
console.log(arr[i]);
}
// RIGHT:
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// Mistake 2: Substring/Slice
const str = "hello";
// str.substring(0, 3) = "hel" (3 characters, indices 0,1,2)
// NOT 4 characters!
console.log(str.substring(0, 3)); // "hel"
// To get first n characters:
function firstN(s, n) {
return s.substring(0, n); // NOT n+1
}
// Mistake 3: Binary search
function binarySearchWrong(arr, target) {
let left = 0;
let right = arr.length; // WRONG! right should be length - 1
while (left < right) { // Changed condition due to wrong init
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid;
}
return -1;
}
function binarySearchCorrect(arr, target) {
let left = 0;
let right = arr.length - 1; // RIGHT!
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// Mistake 4: Range calculations
function sumRange(arr, start, end) {
// Should sum arr[start] through arr[end] inclusive
let sum = 0;
// WRONG: misses arr[end]
for (let i = start; i < end; i++) {
sum += arr[i];
}
// RIGHT:
for (let i = start; i <= end; i++) {
sum += arr[i];
}
return sum;
}
// Mistake 5: Sliding window
function maxSumSubarray(arr, k) {
if (arr.length < k) return 0;
let maxSum = 0;
// WRONG: i < arr.length doesn't guarantee window fits
for (let i = 0; i < arr.length; i++) {
const window = arr.slice(i, i + k);
if (window.length === k) {
maxSum = Math.max(maxSum, window.reduce((a,b) => a+b, 0));
}
}
// RIGHT: i <= arr.length - k
for (let i = 0; i <= arr.length - k; i++) {
const window = arr.slice(i, i + k);
maxSum = Math.max(maxSum, window.reduce((a,b) => a+b, 0));
}
return maxSum;
}
Example: Edge case testing for off-by-one
// Test with boundary values
function testOffByOne() {
// Test array of size 0, 1, 2
console.log(reverse([])); // []
console.log(reverse([1])); // [1]
console.log(reverse([1, 2])); // [2, 1]
// Test with exact boundary indices
const arr = [1, 2, 3, 4, 5];
console.log(getRange(arr, 0, 0)); // [1]
console.log(getRange(arr, 0, 4)); // [1,2,3,4,5]
console.log(getRange(arr, 4, 4)); // [5]
}
// Reverse array in-place
function reverse(arr) {
let left = 0;
let right = arr.length - 1; // Not arr.length!
while (left < right) { // Not <=, would swap middle twice
[arr[left], arr[right]] = [arr[right], arr[left]];
left++;
right--;
}
return arr;
}
// Get subarray [start, end] inclusive
function getRange(arr, start, end) {
// Validate bounds
if (start < 0 || end >= arr.length || start > end) {
return [];
}
// slice is [start, end), so add 1 to end
return arr.slice(start, end + 1);
}
// Matrix traversal
function spiralOrder(matrix) {
if (!matrix.length) return [];
const result = [];
let top = 0, bottom = matrix.length - 1;
let left = 0, right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
// Top row: left to right
for (let i = left; i <= right; i++) {
result.push(matrix[top][i]);
}
top++;
// Right column: top to bottom
for (let i = top; i <= bottom; i++) {
result.push(matrix[i][right]);
}
right--;
// Bottom row: right to left (if still valid)
if (top <= bottom) {
for (let i = right; i >= left; i--) {
result.push(matrix[bottom][i]);
}
bottom--;
}
// Left column: bottom to top (if still valid)
if (left <= right) {
for (let i = bottom; i >= top; i--) {
result.push(matrix[i][left]);
}
left++;
}
}
return result;
}
7. Empty Input Validation
| Input Type | Empty Check | Why It Matters | Behavior |
|---|---|---|---|
| Array | arr.length === 0 |
Prevent index out of bounds | Many operations fail on empty array |
| String | s === '' || s.length === 0 |
charAt(0) returns empty string | String methods work but may give unexpected results |
| Object | Object.keys(obj).length === 0 |
Distinguish empty from non-empty objects | Empty object is truthy |
| Map/Set | map.size === 0 |
Check before iterating | size property, not length |
Example: Empty input handling
// Array operations with empty input
function findMax(arr) {
// Handle empty array
if (arr.length === 0) {
return null; // or throw error, or return -Infinity
}
return Math.max(...arr);
}
// Two pointers with empty array
function twoSum(arr, target) {
if (arr.length < 2) {
return []; // Need at least 2 elements
}
let left = 0;
let right = arr.length - 1;
while (left < right) {
const sum = arr[left] + arr[right];
if (sum === target) return [left, right];
if (sum < target) left++;
else right--;
}
return [];
}
// String operations
function firstChar(s) {
if (s.length === 0) {
return ''; // or null, depending on requirements
}
return s[0]; // or s.charAt(0)
}
// Linked list operations
function reverseList(head) {
// Empty list or single node
if (!head || !head.next) {
return head;
}
let prev = null;
let curr = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// Tree operations
function maxDepth(root) {
// Empty tree
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
// Graph operations
function hasCycle(graph, start) {
// Empty graph
if (!graph || graph.size === 0) {
return false;
}
// Node not in graph
if (!graph.has(start)) {
return false;
}
const visited = new Set();
const recStack = new Set();
function dfs(node) {
visited.add(node);
recStack.add(node);
const neighbors = graph.get(node) || [];
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
if (dfs(neighbor)) return true;
} else if (recStack.has(neighbor)) {
return true;
}
}
recStack.delete(node);
return false;
}
return dfs(start);
}
// Map/Set operations
function mostFrequent(arr) {
if (arr.length === 0) return null;
const freq = new Map();
for (const num of arr) {
freq.set(num, (freq.get(num) || 0) + 1);
}
let maxCount = 0;
let result = null;
for (const [num, count] of freq) {
if (count > maxCount) {
maxCount = count;
result = num;
}
}
return result;
}
8. Single Element Edge Cases
| Scenario | Issue | Example | Solution |
|---|---|---|---|
| Array with 1 Element | Two-pointer algorithms fail | QuickSort partition, two sum | Check arr.length < 2 |
| Single Node List | No next pointer to check | Reverse list, detect cycle | Check !head.next |
| Single Node Tree | No children to traverse | Tree traversal, height calculation | Base case handles it |
| Single Character String | No substring possible | Palindrome check, substring search | Return early or handle specially |
Example: Single element handling
// Binary search with single element
function binarySearch(arr, target) {
// Single element
if (arr.length === 1) {
return arr[0] === target ? 0 : -1;
}
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// Merge sort with single element
function mergeSort(arr) {
// Base case: 0 or 1 element
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);
}
// Palindrome with single character
function isPalindrome(s) {
// Single character is always palindrome
if (s.length <= 1) return true;
let left = 0;
let right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) return false;
left++;
right--;
}
return true;
}
// Linked list cycle with single node
function hasCycle(head) {
// No cycle in empty or single node (no next)
if (!head || !head.next) return false;
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true;
}
return false;
}
// LCA with single node tree
function lowestCommonAncestor(root, p, q) {
// Single node tree
if (!root) return null;
if (root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
if (left && right) return root;
return left || right;
}
Example: Testing with minimal inputs
// Always test with edge cases:
// - Empty input
// - Single element
// - Two elements
// - Duplicate elements
function testQuickSort() {
console.log(quickSort([])); // []
console.log(quickSort([1])); // [1]
console.log(quickSort([2, 1])); // [1, 2]
console.log(quickSort([1, 1])); // [1, 1]
console.log(quickSort([3, 1, 2])); // [1, 2, 3]
}
// Robust quicksort
function quickSort(arr) {
if (arr.length <= 1) return arr; // Handles empty and single
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
return [...quickSort(left), ...middle, ...quickSort(right)];
}
// Median of array
function findMedian(arr) {
if (arr.length === 0) return null;
if (arr.length === 1) return arr[0];
const sorted = [...arr].sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[mid - 1] + sorted[mid]) / 2;
}
return sorted[mid];
}
// Kth largest element
function findKthLargest(arr, k) {
if (arr.length === 0) return null;
if (k < 1 || k > arr.length) return null;
// Single element: only valid if k === 1
if (arr.length === 1) {
return k === 1 ? arr[0] : null;
}
const sorted = [...arr].sort((a, b) => b - a);
return sorted[k - 1];
}
// Longest substring without repeating
function lengthOfLongestSubstring(s) {
if (s.length <= 1) return s.length;
const seen = new Set();
let left = 0;
let maxLen = 0;
for (let right = 0; right < s.length; right++) {
while (seen.has(s[right])) {
seen.delete(s[left]);
left++;
}
seen.add(s[right]);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
9. Duplicate Handling Strategies
| Strategy | Method | Use Case | Complexity |
|---|---|---|---|
| Set Deduplication | new Set(arr) |
Remove all duplicates | O(n) time, O(n) space |
| Hash Map Counting | Map<value, count> | Find frequency, most frequent | O(n) time, O(n) space |
| Two Pointer (Sorted) | Compare adjacent elements | Remove duplicates in-place | O(n) time, O(1) space |
| Floyd's Cycle Detection | Fast and slow pointers | Find duplicate in [1..n] array | O(n) time, O(1) space |
Example: Various duplicate handling approaches
// Remove duplicates - Set approach
function removeDuplicatesSet(arr) {
return [...new Set(arr)];
}
// Remove duplicates in-place (sorted array)
function removeDuplicatesSorted(arr) {
if (arr.length <= 1) return arr.length;
let writeIndex = 1;
for (let i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
arr[writeIndex] = arr[i];
writeIndex++;
}
}
return writeIndex; // New length
}
// Find all duplicates
function findDuplicates(arr) {
const seen = new Set();
const duplicates = new Set();
for (const num of arr) {
if (seen.has(num)) {
duplicates.add(num);
}
seen.add(num);
}
return [...duplicates];
}
// Count frequency of each element
function countFrequency(arr) {
const freq = new Map();
for (const num of arr) {
freq.set(num, (freq.get(num) || 0) + 1);
}
return freq;
}
// Find duplicate in [1..n] array - O(1) space
function findDuplicateCycle(nums) {
// Array has n+1 elements from range [1, n]
// Guaranteed to have at least one duplicate
// Floyd's cycle detection
let slow = nums[0];
let fast = nums[0];
// Find intersection point in cycle
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow !== fast);
// Find entrance to cycle (duplicate number)
slow = nums[0];
while (slow !== fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
// Remove duplicates allowing at most k occurrences
function removeDuplicatesK(arr, k) {
if (arr.length <= k) return arr.length;
let writeIndex = k;
for (let i = k; i < arr.length; i++) {
if (arr[i] !== arr[writeIndex - k]) {
arr[writeIndex] = arr[i];
writeIndex++;
}
}
return writeIndex;
}
Example: Duplicate detection patterns
// Contains duplicate (simple check)
function containsDuplicate(arr) {
return new Set(arr).size !== arr.length;
}
// Contains nearby duplicate (within k distance)
function containsNearbyDuplicate(arr, k) {
const seen = new Map();
for (let i = 0; i < arr.length; i++) {
if (seen.has(arr[i])) {
if (i - seen.get(arr[i]) <= k) {
return true;
}
}
seen.set(arr[i], i);
}
return false;
}
// Find all duplicates in array where 1 <= arr[i] <= n
function findAllDuplicates(arr) {
const result = [];
// Use index as hash: mark visited by negating
for (let i = 0; i < arr.length; i++) {
const index = Math.abs(arr[i]) - 1;
if (arr[index] < 0) {
// Already seen this number
result.push(index + 1);
} else {
// Mark as seen
arr[index] = -arr[index];
}
}
// Restore array (optional)
for (let i = 0; i < arr.length; i++) {
arr[i] = Math.abs(arr[i]);
}
return result;
}
// Group anagrams (strings with same character frequency)
function groupAnagrams(strs) {
const groups = new Map();
for (const str of strs) {
// Sort characters to get key
const key = str.split('').sort().join('');
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key).push(str);
}
return Array.from(groups.values());
}
// Detect duplicate subtrees
function findDuplicateSubtrees(root) {
const seen = new Map();
const duplicates = [];
function serialize(node) {
if (!node) return '#';
const left = serialize(node.left);
const right = serialize(node.right);
const serial = `${node.val},${left},${right}`;
const count = seen.get(serial) || 0;
if (count === 1) {
duplicates.push(node);
}
seen.set(serial, count + 1);
return serial;
}
serialize(root);
return duplicates;
}
10. Overflow Underflow Prevention
| Operation | Risk | Check Before | Alternative |
|---|---|---|---|
| a + b | Overflow if both positive and large | a > MAX - b |
Use BigInt |
| a * b | Overflow if product exceeds MAX | a > MAX / b |
Use BigInt or check bounds |
| a - b | Underflow if a is small, b is large | a < MIN + b |
Check sign compatibility |
| (left + right) / 2 | Overflow in sum | Use left + (right - left) / 2 |
Prevents intermediate overflow |
Example: Overflow prevention techniques
const MAX = Number.MAX_SAFE_INTEGER;
const MIN = Number.MIN_SAFE_INTEGER;
// Safe addition
function add(a, b) {
// Check for overflow
if (a > 0 && b > 0 && a > MAX - b) {
return BigInt(a) + BigInt(b);
}
// Check for underflow
if (a < 0 && b < 0 && a < MIN - b) {
return BigInt(a) + BigInt(b);
}
return a + b;
}
// Safe multiplication
function multiply(a, b) {
if (a === 0 || b === 0) return 0;
// Check overflow
if (Math.abs(a) > MAX / Math.abs(b)) {
return BigInt(a) * BigInt(b);
}
return a * b;
}
// Binary search mid point (safe from overflow)
function safeMid(left, right) {
// WRONG: (left + right) / 2 can overflow
// RIGHT: left + (right - left) / 2
return left + Math.floor((right - left) / 2);
}
// Safe average of two numbers
function average(a, b) {
// WRONG: (a + b) / 2 can overflow
// RIGHT: a/2 + b/2 or use safe mid formula
return a / 2 + b / 2;
}
// Power calculation with overflow detection
function power(base, exp) {
if (exp === 0) return 1;
if (base === 0) return 0;
if (exp === 1) return base;
let result = 1;
for (let i = 0; i < exp; i++) {
// Check if next multiplication would overflow
if (result > MAX / base) {
// Switch to BigInt
let bigResult = BigInt(result);
for (let j = i; j < exp; j++) {
bigResult *= BigInt(base);
}
return bigResult;
}
result *= base;
}
return result;
}
// Factorial with early switch to BigInt
function factorial(n) {
if (n <= 1) return 1;
// Switch to BigInt early since factorial grows fast
let result = 1n;
for (let i = 2n; i <= n; i++) {
result *= i;
}
return result;
}
// Sum of array with overflow protection
function sum(arr) {
let total = 0;
for (const num of arr) {
// Check before adding
if (num > 0 && total > MAX - num) {
// Convert to BigInt for rest of sum
let bigTotal = BigInt(total);
for (let i = arr.indexOf(num); i < arr.length; i++) {
bigTotal += BigInt(arr[i]);
}
return bigTotal;
}
total += num;
}
return total;
}
// Range sum query with overflow check
function rangeSum(arr, left, right) {
if (left > right || left < 0 || right >= arr.length) {
return 0;
}
let sum = 0;
for (let i = left; i <= right; i++) {
const newSum = sum + arr[i];
// Check if overflow occurred
if (sum > 0 && arr[i] > 0 && newSum < sum) {
console.warn('Overflow detected in range sum');
// Use BigInt for safety
return arr.slice(left, right + 1)
.reduce((a, b) => BigInt(a) + BigInt(b), 0n);
}
sum = newSum;
}
return sum;
}
Warning: JavaScript numbers lose precision beyond 2^53 - 1. For calculations involving very large numbers, use BigInt from the start rather than trying to detect overflow. Remember:
(left + right) / 2 can overflow - use left + (right - left) / 2 instead.
Summary: Common Pitfalls and Edge Cases
- Integer Overflow: JavaScript safe integer range is ±(2^53 - 1) - use BigInt for larger values or check before operations
- Overflow Detection: For a + b, check
a > MAX - b; for a * b, checka > MAX / bbefore operating - Floating Point Precision:
0.1 + 0.2 !== 0.3- use epsilon comparison or work with integers (cents not dollars) - Epsilon Comparison:
Math.abs(a - b) < EPSILONinstead ofa === bfor floats - Array Bounds: JavaScript returns
undefinedfor out-of-bounds, not error - always validate indices - Loop Boundaries: Use
i < arr.lengthnot<=; binary search:right = length - 1notlength - Null Safety: Use optional chaining
obj?.propand nullish coalescing??for safe navigation - Linked List/Tree: Always check
if (!node)before accessingnode.nextornode.val - Stack Overflow: JavaScript call stack limited to ~10,000-50,000 frames - use iteration for deep recursion
- Recursion Conversion: Single recursive call → simple loop; tree recursion → explicit stack with state
- Off-by-One: Common in loops (
<=vs<), binary search init, and slice/substring (exclusive end) - Substring Behavior:
s.substring(0, n)gives n characters (indices 0 to n-1), not n+1 - Empty Input: Check
arr.length === 0before operations - many algorithms fail on empty input - Single Element: Arrays with 1 element break two-pointer algorithms - add early return for
length < 2 - Duplicate Detection: Set for deduplication O(n) space, sorted two-pointer for O(1) space, Floyd's for array with specific constraints
- Overflow Prevention: Binary search mid: use
left + (right - left) / 2not(left + right) / 2