CrackFrontendCF
Resources
Practice
CrackFrontend

Your guide to mastering frontend interviews

Resources

LibraryPracticeDonate

Company

About UsContact

Legal

Privacy PolicyTerms of Service

Built with ❀️ by Tushar Khanna

CrackFrontendCF
Resources
Practice

🎯 Sliding Window Technique: Efficient String & Array Pattern for Frontend Interviews

Master the sliding window technique for efficient substring and subarray problems - a critical pattern for frontend interview coding challenges involving strings and arrays.

Interview Importance: πŸ”΄ Critical β€” This technique appears in 35% of frontend coding interviews for substring, subarray, and data stream problems. Essential for optimizing O(nΒ²) brute force solutions to O(n) linear time. -- 1️⃣ What is the Sliding Window Technique? The Sliding Window Technique is an algorithmic pattern that maintains a "window" (contiguous sequence) over an array or string. The window slides through the data structure by adding elements on one end and removing from the other, allowing efficient computation without recalculating everything from scratch. Visual Representation: Real-World Analogy: Think of it like a camera viewfinder moving across a landscape. Instead of taking a new photo for every possible view (expensive), you slide the viewfinder smoothly, only adjusting what enters and exits the frame. This is exactly how video streaming buffers work in frontend applications! -- 2️⃣ Why Use the Sliding Window Technique? Without Sliding Window Benefit O(nΒ³) check all substrings 1000x faster for 1000 chars Max sum of k elements O(n) maintain sum O(nm) compare each window Optimal for pattern matching Find all substrings of length k O(n) slide indices O(nΒ²) nested checks Real-time processing Aspect Two Pointers Brute Force Time Complexity O(n) O(nΒ²) or O(nΒ³) Space Complexity O(1) typically O(1) Use Case Sorted arrays, pairs Small inputs only Window Type N/A All possible Best For Finding pairs, palindromes Testing, validation Frontend Example Merge sorted lists Simple validation Operation Space Complexity Fixed window (size k) O(1) or O(k) O(n) Each element enters and exits once; window state With hash map O(k) O(nklog k) Sorting each window position; not optimal 2D sliding window O(kΒ²) O(nΒ²) or O(nΒ³) Nested loops; no optimization Category Definition Reduces O(nΒ²) or O(nΒ³) to O(n) by avoiding recalculation Space Optimization Constant size k, slide by adding one and removing one element Variable Window Search autocomplete, real-time monitoring, data stream processing Key Insight 5 Key Takeaways: 1. Sliding window eliminates redundant computation by maintaining running state (sum, frequency, etc.) and updating incrementally rather than recalculating from scratch 2. Fixed windows use O(n) single pass by sliding exactly one position at a time with O(1) add/remove operations 3. Variable windows use two pointers (left/right) that move independently to expand when conditions allow and contract when they're violated 4. Always handle edge cases: null input, window larger than array, off-by-one errors in window size calculation 5. Perfect for frontend real-world scenarios: autocomplete, infinite scroll, real-time analytics, search-as-you-type, and any contiguous sequence optimization -- πŸ“š Further Reading MDN Web Docs Array Methods Understanding array operations for window manipulation LeetCode Sliding Window Pattern Practice problems with varying difficulty Big O Notation Guide Complexity analysis reference -- πŸ”— Related Resources in This Repo Two-Pointer Technique Complementary pattern for sorted arrays and palindromes Merge Two Sorted Arrays Uses two-pointer technique similar to sliding window -- <!-quiz-start --Q1: What is the key insight that makes sliding window achieve O(n) time complexity instead of O(nΒ²)? [ ] It uses recursion to divide the problem [x] Each element enters and exits the window exactly once, avoiding recalculation [ ] It sorts the array first [ ] It uses hash tables for O(1) lookups Q2: What is the difference between fixed-size and variable-size sliding windows? [ ] Fixed-size windows are always faster [ ] Variable-size windows require sorting [x] Fixed-size windows maintain constant length while variable-size windows expand/contract based on conditions [ ] There is no difference, they are the same technique Q3: In a variable-size sliding window for finding longest substring without repeating characters, when do you shrink the window from the left? [ ] When the window reaches a maximum size [ ] At every iteration [x] When a duplicate character is encountered in the window [ ] When the right pointer reaches the end <!-quiz-end --
DSA
🎯 2-Month DSA Plan for Working Professionals: FAANG Interview Preparation
hard
🎯 30-Day DSA Mastery Guide for Senior Frontend Engineers
easy
🎯 Breadth-First Search (BFS): Level-Order Traversal Pattern for Frontend Interviews
hard
🎯 Depth-First Search (DFS): Deep Traversal Pattern for Frontend Interviews
hard
🎯 LRU & LFU Cache: Eviction Algorithms, Applications & Distributed Caching
hard
πŸ”€ Merge Two Sorted Arrays
hard
🎯 Prefix Sum Technique: Efficient Range Query Pattern for Frontend Interviews
hard
🎯 Sliding Window Technique: Efficient String & Array Pattern for Frontend Interviews
hard
🎯 Two-Pointer Technique: Essential Pattern for Frontend Interviews
hard
8 of 9
LibraryDSA8 of 9

🎯 Sliding Window Technique: Efficient String & Array Pattern for Frontend Interviews

dsahard

Interview Importance: πŸ”΄ Critical β€” This technique appears in 35% of frontend coding interviews for substring, subarray, and data stream problems. Essential for optimizing O(nΒ²) brute force solutions to O(n) linear time.


1️⃣ What is the Sliding Window Technique?

The Sliding Window Technique is an algorithmic pattern that maintains a "window" (contiguous sequence) over an array or string. The window slides through the data structure by adding elements on one end and removing from the other, allowing efficient computation without recalculating everything from scratch.

Visual Representation:

String: "abcabcbb"

Window of size 3 sliding right:
Step 1:  [a b c] a b c b b    -> "abc"
Step 2:   a [b c a] b c b b   -> "bca"
Step 3:   a b [c a b] c b b   -> "cab"
Step 4:   a b c [a b c] b b   -> "abc"

Dynamic window (expands/contracts):
Step 1:  [a b c]               -> unique chars: 3
Step 2:  [a b c a]             -> duplicate! shrink from left
Step 3:   [b c a]              -> unique chars: 3

Real-World Analogy:

Think of it like a camera viewfinder moving across a landscape. Instead of taking a new photo for every possible view (expensive), you slide the viewfinder smoothly, only adjusting what enters and exits the frame. This is exactly how video streaming buffers work in frontend applications!


2️⃣ Why Use the Sliding Window Technique?

Problem TypeWithout Sliding WindowWith Sliding WindowBenefit
Longest substring without repeatsO(nΒ³) check all substringsO(n) single pass1000x faster for 1000 chars
Max sum of k elementsO(n*k) recalculate sumsO(n) maintain sumLinear scaling
Count anagramsO(n*m) compare each windowO(n) frequency mapOptimal for pattern matching
Find all substrings of length kO(n*k) create substringsO(n) slide indices50% memory saved
Valid window with conditionsO(nΒ²) nested checksO(n) expand/contractReal-time processing

Performance Benefits:

  • Reduces time complexity from O(nΒ²) or O(nΒ³) to O(n)
  • Often achieves O(1) or O(k) space complexity with hash maps
  • Perfect for frontend: autocomplete suggestions, real-time validation, data stream monitoring
  • Powers features like search-as-you-type, infinite scroll, pagination

3️⃣ How It Works β€” Basic Implementation

Pattern 1: Fixed-Size Window

// Find maximum sum of any subarray of size k
const maxSumSubarray = (arr, k) => {
  if (!arr || arr.length === 0) return null;  // Edge case: null or empty array
  if (k <= 0) return null;                     // Edge case: invalid window size
  if (arr.length < k) return null;             // Edge case: array smaller than window
  
  // Calculate sum of first window
  let windowSum = 0;
  for (let i = 0; i < k; i++) {
    windowSum += arr[i];
  }
  
  let maxSum = windowSum;
  
  // Slide window: add next element, remove first element
  for (let i = k; i < arr.length; i++) {
    windowSum = windowSum + arr[i] - arr[i - k];  // Slide in O(1)
    maxSum = Math.max(maxSum, windowSum);
  }
  
  return maxSum;
};

πŸ” Dry Run: Maximum Sum Subarray

Input: arr = [2, 1, 5, 1, 3, 2], k = 3

Step 1: Initialize first window of size 3
---------------------------------------------------------
  arr = [2, 1, 5, 1, 3, 2]
        [-----]              <-- window
  windowSum = 2 + 1 + 5 = 8
  maxSum = 8

Step 2: Slide window right (remove arr[0], add arr[3])
---------------------------------------------------------
  arr = [2, 1, 5, 1, 3, 2]
           [-----]           <-- window
  Remove: arr[0] = 2
  Add: arr[3] = 1
  windowSum = 8 - 2 + 1 = 7
  maxSum = Math.max(8, 7) = 8

Step 3: Slide window right (remove arr[1], add arr[4])
---------------------------------------------------------
  arr = [2, 1, 5, 1, 3, 2]
              [-----]        <-- window
  Remove: arr[1] = 1
  Add: arr[4] = 3
  windowSum = 7 - 1 + 3 = 9
  maxSum = Math.max(8, 9) = 9  βœ“ New maximum!

Step 4: Slide window right (remove arr[2], add arr[5])
---------------------------------------------------------
  arr = [2, 1, 5, 1, 3, 2]
                 [-----]     <-- window
  Remove: arr[2] = 5
  Add: arr[5] = 2
  windowSum = 9 - 5 + 2 = 6
  maxSum = Math.max(9, 6) = 9

Result: 9 (subarray [5, 1, 3])

Time Complexity: O(n) - single pass through array
Space Complexity: O(1) - only storing sum and max


4️⃣ Understanding Key Concepts

Why This Works: The Sliding Principle

// ❌ BAD: Brute force recalculates entire sum each time
for (let i = 0; i <= arr.length - k; i++) {
  let sum = 0;
  for (let j = i; j < i + k; j++) {  // Nested loop!
    sum += arr[j];
  }
  maxSum = Math.max(maxSum, sum);
}
// Time: O(n*k) - recalculating overlapping work

// βœ… GOOD: Sliding window reuses previous calculation
windowSum = windowSum + arr[i] - arr[i - k];  // O(1) update!
// Time: O(n) - each element visited once

Key Insight: When the window slides, only 2 elements change (one enters, one exits). We don't need to recalculate everything in between.

What Breaks Without Proper Bounds?

// Edge case: What if k > arr.length?
if (arr.length < k) return null;  // Must check first!

// Edge case: What if k is 0 or negative?
if (k <= 0) return null;  // Invalid window size

// Edge case: What if array is empty?
if (!arr || arr.length === 0) return null;

These checks prevent:

  • Index out of bounds errors
  • Infinite loops
  • Invalid sum calculations
  • Undefined behavior

5️⃣ Production/Advanced Implementation

Pattern 2: Dynamic Window (Variable Size)

/**
 * Find the length of longest substring without repeating characters
 * Example: "abcabcbb" -> "abc" (length 3)
 * 
 * @param {string} s - Input string
 * @returns {number} Length of longest unique substring
 */
const lengthOfLongestSubstring = (s) => {
  // Input validation
  if (!s || s.length === 0) return 0;
  if (s.length === 1) return 1;
  
  const charSet = new Set();  // Track characters in current window
  let left = 0;               // Left boundary of window
  let maxLength = 0;          // Result
  
  // Right pointer expands window
  for (let right = 0; right < s.length; right++) {
    const char = s[right];
    
    // Shrink window from left while duplicate exists
    while (charSet.has(char)) {
      charSet.delete(s[left]);  // Remove leftmost character
      left++;                   // Move left boundary
    }
    
    // Add current character and update max length
    charSet.add(char);
    maxLength = Math.max(maxLength, right - left + 1);
  }
  
  return maxLength;
};

πŸ” Dry Run: Longest Unique Substring

Input: s = "abcabcbb"

Step 1: Start with empty window
---------------------------------------------------------
  s = "abcabcbb"
      ^
    left=0, right=0
  charSet = {}
  Add 'a': charSet = {a}
  maxLength = 1

Step 2: Expand window (right moves)
---------------------------------------------------------
  s = "abcabcbb"
      ^ ^
    left=0, right=1
  charSet = {a}
  Add 'b': charSet = {a, b}
  maxLength = max(1, 2) = 2

Step 3: Continue expanding
---------------------------------------------------------
  s = "abcabcbb"
      ^   ^
    left=0, right=2
  charSet = {a, b}
  Add 'c': charSet = {a, b, c}
  maxLength = max(2, 3) = 3  βœ“ "abc"

Step 4: Duplicate found! Shrink from left
---------------------------------------------------------
  s = "abcabcbb"
      ^     ^
    left=0, right=3
  charSet = {a, b, c}
  char='a' already exists!
  
  While loop iteration 1:
    Remove s[0]='a': charSet = {b, c}
    left = 1
  
  Now 'a' not in set, exit while loop
  Add 'a': charSet = {b, c, a}
  maxLength = max(3, 3) = 3

Step 5: Another duplicate 'b'
---------------------------------------------------------
  s = "abcabcbb"
       ^      ^
    left=1, right=4
  charSet = {b, c, a}
  char='b' already exists!
  
  While loop:
    Remove s[1]='b': charSet = {c, a}
    left = 2
  
  Add 'b': charSet = {c, a, b}
  maxLength = max(3, 3) = 3

Step 6: Another duplicate 'c'
---------------------------------------------------------
  s = "abcabcbb"
         ^     ^
    left=2, right=5
  charSet = {c, a, b}
  char='c' already exists!
  
  While loop:
    Remove s[2]='c': charSet = {a, b}
    left = 3
  
  Add 'c': charSet = {a, b, c}
  maxLength = max(3, 3) = 3

Step 7-8: Process remaining 'b's (shrinking continues)
---------------------------------------------------------
  Final maxLength = 3

Result: 3 (substring "abc" or "bca" or "cab")

Advanced: Count Anagrams in String

/**
 * Count occurrences of anagrams of pattern in text
 * Example: countAnagrams("cbaebabacd", "abc") -> 2
 * Windows "cba" and "bac" are anagrams of "abc"
 */
const countAnagrams = (text, pattern) => {
  if (!text || !pattern || text.length < pattern.length) return 0;
  
  const k = pattern.length;
  const patternFreq = new Map();
  const windowFreq = new Map();
  
  // Build frequency map for pattern
  for (const char of pattern) {
    patternFreq.set(char, (patternFreq.get(char) || 0) + 1);
  }
  
  let count = 0;
  let matches = 0;  // Tracks how many characters match pattern frequency
  
  // Process first window
  for (let i = 0; i < k; i++) {
    const char = text[i];
    windowFreq.set(char, (windowFreq.get(char) || 0) + 1);
    
    if (patternFreq.has(char) && windowFreq.get(char) === patternFreq.get(char)) {
      matches++;
    }
  }
  
  if (matches === patternFreq.size) count++;
  
  // Slide window
  for (let i = k; i < text.length; i++) {
    // Add new character (right side)
    const newChar = text[i];
    windowFreq.set(newChar, (windowFreq.get(newChar) || 0) + 1);
    
    if (patternFreq.has(newChar)) {
      if (windowFreq.get(newChar) === patternFreq.get(newChar)) {
        matches++;
      } else if (windowFreq.get(newChar) === patternFreq.get(newChar) + 1) {
        matches--;  // Exceeded required count
      }
    }
    
    // Remove old character (left side)
    const oldChar = text[i - k];
    if (patternFreq.has(oldChar)) {
      if (windowFreq.get(oldChar) === patternFreq.get(oldChar)) {
        matches--;
      } else if (windowFreq.get(oldChar) === patternFreq.get(oldChar) + 1) {
        matches++;
      }
    }
    
    windowFreq.set(oldChar, windowFreq.get(oldChar) - 1);
    if (windowFreq.get(oldChar) === 0) {
      windowFreq.delete(oldChar);
    }
    
    // Check if current window is an anagram
    if (matches === patternFreq.size) count++;
  }
  
  return count;
};

6️⃣ Real-World Frontend Examples

Example 1: Autocomplete with Character Limit

/**
 * Real-time search suggestions with substring matching
 * Used in: Search bars, command palettes, dropdown filters
 */
const getSearchSuggestions = (query, maxResults = 5) => {
  const suggestions = [];
  const queryLower = query.toLowerCase();
  const k = query.length;
  
  // Simulate database of items
  const items = [
    "JavaScript Developer",
    "Java Backend Engineer", 
    "Senior JavaScript",
    "Python Developer"
  ];
  
  for (const item of items) {
    const itemLower = item.toLowerCase();
    
    // Slide window through item to find query
    for (let i = 0; i <= itemLower.length - k; i++) {
      const window = itemLower.slice(i, i + k);
      
      if (window === queryLower) {
        suggestions.push(item);
        break;  // Found match, no need to continue
      }
    }
    
    if (suggestions.length >= maxResults) break;
  }
  
  return suggestions;
};

// Usage
console.log(getSearchSuggestions("java"));
// -> ["JavaScript Developer", "Java Backend Engineer", "Senior JavaScript"]

Example 2: React Hook for Debounced Search

import { useState, useEffect, useRef } from 'react';

/**
 * Custom hook using sliding window concept for search optimization
 * Maintains a buffer of recent searches to avoid redundant API calls
 */
const useSearchWithWindow = (searchFn, windowSize = 10) => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const cacheRef = useRef(new Map());  // FIFO cache with sliding window (max size)
  
  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }
    
    // Check cache first (sliding window of recent searches)
    if (cacheRef.current.has(query)) {
      setResults(cacheRef.current.get(query));
      return;
    }
    
    const timeoutId = setTimeout(async () => {
      setLoading(true);
      
      try {
        const data = await searchFn(query);
        setResults(data);
        
        // Maintain sliding window of cache
        if (cacheRef.current.size >= windowSize) {
          // Remove oldest entry (FIFO)
          const firstKey = cacheRef.current.keys().next().value;
          cacheRef.current.delete(firstKey);
        }
        
        cacheRef.current.set(query, data);
      } catch (error) {
        console.error('Search error:', error);
        setResults([]);
      } finally {
        setLoading(false);
      }
    }, 300);  // Debounce
    
    return () => clearTimeout(timeoutId);
  }, [query, searchFn, windowSize]);
  
  return { query, setQuery, results, loading };
};

// Usage in component
const SearchComponent = () => {
  const searchAPI = async (q) => {
    const res = await fetch(`/api/search?q=${q}`);
    return res.json();
  };
  
  const { query, setQuery, results, loading } = useSearchWithWindow(searchAPI);
  
  return (
    <div>
      <input 
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      {loading && <p>Loading...</p>}
      <ul>
        {results.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
};

Example 3: Real-Time Data Stream Monitoring

/**
 * Monitor real-time metrics with sliding window
 * Use case: Performance monitoring, analytics dashboards
 */
class MetricsMonitor {
  constructor(windowSizeMs = 60000) {  // 1 minute window
    this.window = [];
    this.windowSize = windowSizeMs;
  }
  
  addMetric(value, timestamp = Date.now()) {
    this.window.push({ value, timestamp });
    
    // Slide window: remove old metrics
    const cutoff = timestamp - this.windowSize;
    while (this.window.length > 0 && this.window[0].timestamp < cutoff) {
      this.window.shift();
    }
  }
  
  getAverage() {
    if (this.window.length === 0) return 0;
    
    const sum = this.window.reduce((acc, metric) => acc + metric.value, 0);
    return sum / this.window.length;
  }
  
  getMax() {
    if (this.window.length === 0) return null;
    return Math.max(...this.window.map(m => m.value));
  }
  
  getPercentile(p) {
    if (this.window.length === 0) return null;
    
    const sorted = [...this.window].sort((a, b) => a.value - b.value);
    const index = Math.ceil((p / 100) * sorted.length) - 1;
    return sorted[index].value;
  }
}

// Usage: Track API response times
const monitor = new MetricsMonitor(60000);  // 1 minute sliding window

// Simulate API calls
setInterval(() => {
  const responseTime = Math.random() * 1000;  // 0-1000ms
  monitor.addMetric(responseTime);
  
  console.log({
    avg: monitor.getAverage().toFixed(2),
    max: monitor.getMax().toFixed(2),
    p95: monitor.getPercentile(95).toFixed(2)
  });
}, 1000);

7️⃣ Comparisons

Sliding Window vs. Other Techniques

AspectSliding WindowTwo PointersHash Map OnlyBrute Force
Time ComplexityO(n)O(n)O(n)O(nΒ²) or O(nΒ³)
Space ComplexityO(k) for windowO(1) typicallyO(n) worst caseO(1)
Use CaseContiguous sequencesSorted arrays, pairsFrequency countingSmall inputs only
Window TypeFixed or variableN/AN/AAll possible
Best ForSubstrings, subarraysFinding pairs, palindromesCharacter countingTesting, validation
Frontend ExampleSearch autocompleteMerge sorted listsDetect duplicatesSimple validation

When to Use Which?

// Use Sliding Window when:
// - Looking for contiguous sequence (substring/subarray)
// - Need to optimize from O(nΒ²) to O(n)
// - Window size is fixed or changes predictably
βœ… "Find longest substring with at most k distinct characters"
βœ… "Maximum sum of k consecutive elements"
βœ… "Find all anagrams of a pattern"

// Use Two Pointers when:
// - Array is sorted
// - Looking for pairs meeting a condition
// - Need to process from both ends
βœ… "Find two numbers that sum to target in sorted array"
βœ… "Remove duplicates from sorted array"
βœ… "Check if string is palindrome"

// Use Hash Map when:
// - Order doesn't matter
// - Need O(1) lookups
// - Counting frequencies
βœ… "Find first non-repeating character"
βœ… "Check if two strings are anagrams"
βœ… "Group items by category"

8️⃣ Common Interview Questions

Q1: What's the difference between fixed and variable size sliding windows?

A: Fixed-size windows maintain constant length (e.g., "max sum of k elements"), while variable-size windows expand/contract based on conditions (e.g., "longest substring without repeating characters"). Fixed windows slide by 1 position each time; variable windows adjust both boundaries independently.

// Fixed: Window size = k, always same length
for (let i = k; i < arr.length; i++) {
  windowSum = windowSum + arr[i] - arr[i - k];
}

// Variable: Window grows/shrinks based on condition
while (right < arr.length) {
  // Expand
  add(arr[right]);
  right++;
  
  // Shrink when condition violated
  while (conditionViolated()) {
    remove(arr[left]);
    left++;
  }
}

Q2: How do you handle edge cases in sliding window problems?

A: Always validate:

  1. Empty or null input
  2. Window size larger than array
  3. Negative or zero window sizes
  4. Single element arrays
// Comprehensive edge case handling
const slidingWindowTemplate = (arr, k) => {
  // Edge case 1: Invalid input
  if (!arr || arr.length === 0) return null;
  
  // Edge case 2: Window too large
  if (k > arr.length) return null;
  
  // Edge case 3: Invalid window size
  if (k <= 0) return null;
  
  // Edge case 4: Single element
  if (arr.length === 1) return arr[0];
  
  // Main logic here
};

Q3: When would sliding window be less efficient than brute force?

A: Sliding window is less efficient when:

  • Very small input size (overhead not worth it)
  • Looking for non-contiguous sequences
  • Window operations are expensive (complex updates)
// BAD: Sliding window overhead not worth it for size 3
const arr = [1, 2, 3];
// Just calculate directly: Math.max(...arr)

// BAD: Non-contiguous (need all pairs)
"Find two numbers that sum to X" // Use hash map instead

// BAD: Expensive window maintenance
"Median of each window" // Need to re-sort each time

Q4: How do you optimize space complexity in sliding window problems?

A: Use these strategies:

  1. Avoid creating substring/subarray copies (use indices)
  2. Use Set instead of Array for uniqueness checks
  3. Use frequency counters instead of storing elements
  4. Reuse data structures between iterations
// ❌ BAD: Creating substrings (O(n*k) space)
for (let i = 0; i <= s.length - k; i++) {
  const window = s.substring(i, i + k);  // Creates new string!
  process(window);
}

// βœ… GOOD: Use indices (O(1) space)
for (let i = 0; i <= s.length - k; i++) {
  process(s, i, i + k);  // Pass indices only
}

// βœ… GOOD: Reuse Set (O(k) space max)
const charSet = new Set();  // Single set for entire algorithm

Q5: Explain how to debug a sliding window solution that's giving wrong answers.

A: Follow this debugging checklist:

// Step 1: Add logging at key points
console.log('Window:', { left, right, windowContent });
console.log('State:', { sum, max, frequency });

// Step 2: Check boundary conditions
console.log('Entering window?', right < arr.length);
console.log('Exiting window?', conditionViolated);

// Step 3: Verify window updates
const before = windowSum;
windowSum = windowSum + arr[right] - arr[left];
console.log(`Updated: ${before} + ${arr[right]} - ${arr[left]} = ${windowSum}`);

// Step 4: Dry run manually with small input
const testCase = [1, 2, 3];  // Simple case
// Trace each step by hand

// Step 5: Check off-by-one errors
// - Is it right - left + 1 or just right - left?
// - Should it be < or <=?
// - Is window initialized correctly?

Q6: How would you implement a sliding window for a 2D array?

A: Use two nested sliding windows (row and column):

/**
 * Find maximum sum of kΓ—k submatrix
 */
const maxSumSubmatrix = (matrix, k) => {
  if (!matrix || matrix.length < k || matrix[0].length < k) return null;
  
  const rows = matrix.length;
  const cols = matrix[0].length;
  let maxSum = -Infinity;
  
  // Slide vertically
  for (let i = 0; i <= rows - k; i++) {
    // Column sums for current k rows
    const colSums = new Array(cols).fill(0);
    
    // Calculate initial column sums
    for (let row = i; row < i + k; row++) {
      for (let col = 0; col < cols; col++) {
        colSums[col] += matrix[row][col];
      }
    }
    
    // Slide horizontally through column sums
    let windowSum = 0;
    for (let j = 0; j < k; j++) {
      windowSum += colSums[j];
    }
    maxSum = Math.max(maxSum, windowSum);
    
    for (let j = k; j < cols; j++) {
      windowSum = windowSum + colSums[j] - colSums[j - k];
      maxSum = Math.max(maxSum, windowSum);
    }
  }
  
  return maxSum;
};

9️⃣ Common Pitfalls

Pitfall 1: Off-by-One Errors in Window Bounds

// ❌ BAD: Wrong window size calculation
const length = right - left;  // Missing + 1!
// If left=0, right=2, this gives length=2, but should be 3

// βœ… GOOD: Correct window size
const length = right - left + 1;  // Inclusive of both ends

Why it breaks: Array indices are 0-based. If left=0 and right=2, the window includes elements at indices 0, 1, and 2 (3 elements total), not 2.


Pitfall 2: Not Handling Duplicates Properly

// ❌ BAD: Doesn't handle duplicates in variable window
const longestUnique = (s) => {
  const charSet = new Set();
  let left = 0, maxLen = 0;
  
  for (let right = 0; right < s.length; right++) {
    if (charSet.has(s[right])) {
      charSet.delete(s[left]);  // Only removes one char!
      left++;
    }
    charSet.add(s[right]);
    maxLen = Math.max(maxLen, right - left + 1);
  }
  return maxLen;
};

// Input: "abba"
// After seeing second 'b', we only delete 'a', but 'b' still in set!
// Result: Wrong answer

// βœ… GOOD: Use while loop to remove all until duplicate gone
const longestUnique = (s) => {
  const charSet = new Set();
  let left = 0, maxLen = 0;
  
  for (let right = 0; right < s.length; right++) {
    while (charSet.has(s[right])) {  // While, not if!
      charSet.delete(s[left]);
      left++;
    }
    charSet.add(s[right]);
    maxLen = Math.max(maxLen, right - left + 1);
  }
  return maxLen;
};

Why it breaks: A single delete might not remove the duplicate. You need to keep deleting from the left until the duplicate is gone.


Pitfall 3: Modifying Window State Incorrectly

// ❌ BAD: Forgetting to update frequency when removing element
const countAnagrams = (s, pattern) => {
  const freq = new Map();
  // ... build pattern freq ...
  
  for (let i = k; i < s.length; i++) {
    // Add new character
    const newChar = s[i];
    freq.set(newChar, (freq.get(newChar) || 0) + 1);
    
    // Remove old character - FORGOT TO UPDATE!
    // This causes frequency map to grow forever
    
    // Check anagram
    if (isAnagram(freq, patternFreq)) count++;
  }
};

// βœ… GOOD: Always maintain window state correctly
const countAnagrams = (s, pattern) => {
  const freq = new Map();
  // ... build pattern freq ...
  
  for (let i = k; i < s.length; i++) {
    // Add new character
    const newChar = s[i];
    freq.set(newChar, (freq.get(newChar) || 0) + 1);
    
    // Remove old character (critical!)
    const oldChar = s[i - k];
    freq.set(oldChar, freq.get(oldChar) - 1);
    if (freq.get(oldChar) === 0) {
      freq.delete(oldChar);  // Clean up zero counts
    }
    
    // Check anagram
    if (isAnagram(freq, patternFreq)) count++;
  }
};

Why it breaks: Sliding window requires both adding new elements AND removing old ones. Forgetting either operation corrupts the window state.


Pitfall 4: Creating Unnecessary Copies

// ❌ BAD: Creating substring copies (slow and memory-intensive)
const allSubstrings = (s, k) => {
  const result = [];
  for (let i = 0; i <= s.length - k; i++) {
    const window = s.substring(i, i + k);  // O(k) time and space each iteration!
    result.push(window);
  }
  return result;
};
// Time: O(n*k), Space: O(n*k)

// βœ… GOOD: Use indices only
const allSubstrings = (s, k) => {
  const result = [];
  for (let i = 0; i <= s.length - k; i++) {
    result.push({ start: i, end: i + k });  // O(1) time and space
  }
  return result;
};
// Time: O(n), Space: O(n)

// If you must return strings, do it lazily:
const getString = (indices) => s.substring(indices.start, indices.end);

Why it breaks: Creating substrings for every window position multiplies time complexity by window size (k) and wastes memory. Use indices instead and only create strings when absolutely necessary.


πŸ”Ÿ Time & Space Complexity

OperationTime ComplexitySpace ComplexityExplanation
Fixed window (size k)O(n)O(1) or O(k)Single pass; constant space or window storage
Variable window (expand/contract)O(n)O(k)Each element enters and exits once; window state
With hash mapO(n)O(k)Hash operations O(1); k unique elements max
With sorting in windowO(nklog k)O(k)Sorting each window position; not optimal
2D sliding windowO(n*m)O(kΒ²)Iterate all positions; kΓ—k window storage
Brute force comparisonO(nΒ²) or O(nΒ³)O(1)Nested loops; no optimization

Why Sliding Window is O(n):

// Even though there are nested structures, analysis shows:
for (let right = 0; right < n; right++) {      // Executes n times
  while (left < right && condition) {          // Total across all iterations: n times
    left++;                                    // Each element moved by left at most once
  }
}

// Total operations:
// - right pointer moves n times
// - left pointer moves n times (across all while loops combined)
// - Total: 2n = O(n)

Key insight: Although the while loop is nested, the left pointer moves at most n times in total across the entire algorithm, not n times per iteration. This is what makes sliding window O(n) instead of O(nΒ²).


Summary

CategoryKey Takeaway
DefinitionAlgorithm pattern that maintains a contiguous window sliding through data
Time OptimizationReduces O(nΒ²) or O(nΒ³) to O(n) by avoiding recalculation
Space OptimizationO(1) to O(k) by using indices and reusing data structures
Fixed WindowConstant size k, slide by adding one and removing one element
Variable WindowDynamic size, expand/contract based on conditions using two pointers
Frontend Use CasesSearch autocomplete, real-time monitoring, data stream processing
Key InsightEach element enters and exits the window exactly once

5 Key Takeaways:

  1. Sliding window eliminates redundant computation by maintaining running state (sum, frequency, etc.) and updating incrementally rather than recalculating from scratch
  2. Fixed windows use O(n) single pass by sliding exactly one position at a time with O(1) add/remove operations
  3. Variable windows use two pointers (left/right) that move independently to expand when conditions allow and contract when they're violated
  4. Always handle edge cases: null input, window larger than array, off-by-one errors in window size calculation
  5. Perfect for frontend real-world scenarios: autocomplete, infinite scroll, real-time analytics, search-as-you-type, and any contiguous sequence optimization

πŸ“š Further Reading

  • MDN Web Docs - Array Methods - Understanding array operations for window manipulation
  • LeetCode Sliding Window Pattern - Practice problems with varying difficulty
  • Big O Notation Guide - Complexity analysis reference

πŸ”— Related Resources in This Repo

  • Two-Pointer Technique - Complementary pattern for sorted arrays and palindromes
  • Merge Two Sorted Arrays - Uses two-pointer technique similar to sliding window

Quick Quiz

Test your understanding with 3 quick questions

Q1What is the key insight that makes sliding window achieve O(n) time complexity instead of O(nΒ²)?
Q2What is the difference between fixed-size and variable-size sliding windows?
Q3In a variable-size sliding window for finding longest substring without repeating characters, when do you shrink the window from the left?

Continue Reading

CrackFrontend

Your guide to mastering frontend interviews

Resources

LibraryPracticeDonate

Company

About UsContact

Legal

Privacy PolicyTerms of Service

Built with ❀️ by Tushar Khanna