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, check a > MAX / b before 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) < EPSILON instead of a === b for floats
  • Array Bounds: JavaScript returns undefined for out-of-bounds, not error - always validate indices
  • Loop Boundaries: Use i < arr.length not <=; binary search: right = length - 1 not length
  • Null Safety: Use optional chaining obj?.prop and nullish coalescing ?? for safe navigation
  • Linked List/Tree: Always check if (!node) before accessing node.next or node.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 === 0 before 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) / 2 not (left + right) / 2