name: Two Pointers
slug: two-pointers
difficulty_level: 2
pattern_type: algorithm
display_order: 1

description: >
  Use two pointers to traverse data from different positions, often moving
  toward or away from each other. This technique transforms O(n²) brute force
  into O(n) by eliminating redundant comparisons.

when_to_use: |
  - Sorted arrays where you need to find pairs
  - Linked list cycle detection
  - Removing duplicates in-place
  - Partitioning arrays
  - Palindrome checking

metaphor: |
  Imagine two people reading a book from opposite ends, each moving toward the
  middle. The person at the back skips ahead when they find what they're looking
  for, while the person at the front moves forward when they don't match. They
  meet somewhere in the middle, having searched the entire book without either
  person reading the same page twice.

  Another way to think about it: squeezing toothpaste from both ends of the
  tube. You apply pressure from each side, working toward the center until
  you've gotten everything out.

core_concept: |
  The **two pointers** technique eliminates the need for nested loops by
  maintaining two positions that move through the data based on conditions.

  The key insight is that when data has *structure* (like being sorted), you
  can make intelligent decisions about which pointer to move. If the current
  pair is too small, moving the left pointer right increases the sum. If it's
  too large, moving the right pointer left decreases it.

  This reduces O(n²) brute force (checking all pairs) to O(n) because each
  element is visited at most twice—once by each pointer.

visualization: |
  **Example: Find pair with sum = 10 in sorted array**

  ```
  Array: [1, 2, 4, 6, 8, 10]  Target: 10
          L              R

  Step 1: 1 + 10 = 11 > 10  → Sum too large, move R left
          L           R

  Step 2: 1 + 8 = 9 < 10    → Sum too small, move L right
             L        R

  Step 3: 2 + 8 = 10 ✓      → Found! Return [1, 4]
  ```

  **Key insight**: Because the array is sorted, we know exactly which pointer
  to move. Too big? Decrease the larger value. Too small? Increase the smaller.

code_template: |
  def two_pointers(arr: list, target: int) -> list:
      """Two pointers converging from opposite ends."""
      left, right = 0, len(arr) - 1

      while left < right:
          current = arr[left] + arr[right]

          if current == target:
              return [left, right]  # Found!
          elif current < target:
              left += 1   # Need larger sum
          else:
              right -= 1  # Need smaller sum

      return []  # No solution found


  def two_pointers_same_direction(arr: list) -> int:
      """Two pointers moving in same direction (slow/fast)."""
      slow = 0

      for fast in range(len(arr)):
          if some_condition(arr[fast]):
              arr[slow] = arr[fast]
              slow += 1

      return slow  # New length

recognition_signals:
  - "sorted array"
  - "find pair with sum"
  - "two sum"
  - "in-place modification"
  - "remove duplicates"
  - "partition array"
  - "palindrome"
  - "container with most water"
  - "trapping rain water"
  - "move zeros"

common_mistakes:
  - title: Off-by-one with boundaries
    description: |
      Using `<=` instead of `<` when pointers should not overlap causes
      infinite loops or double-counting elements.
    fix: |
      For converging pointers, use `while left < right`. Only use `<=` when
      the same element can be part of the answer twice.

  - title: Not handling duplicates
    description: |
      When the problem asks for unique pairs, forgetting to skip duplicate
      values leads to repeated answers.
    fix: |
      After finding a match, skip over duplicates:
      ```python
      while left < right and arr[left] == arr[left + 1]:
          left += 1
      ```

  - title: Moving both pointers at once
    description: |
      Moving both pointers simultaneously after finding a match can skip
      valid solutions.
    fix: |
      Move one pointer at a time and let the next iteration decide the other.
      After a match, move both only when you've recorded the result.

  - title: Forgetting the sorted requirement
    description: |
      Two pointers only works predictably on sorted data. Applying it to
      unsorted arrays gives wrong results.
    fix: |
      Sort first if needed (adds O(n log n)), or use a hash map approach
      instead if sorting changes the problem semantics.

variations:
  - name: Opposite-direction (converging)
    description: |
      Pointers start at opposite ends and move toward each other. Used for
      pair problems in sorted arrays.
    example: "Two Sum II, Container With Most Water, Valid Palindrome"

  - name: Same-direction (fast-slow)
    description: |
      Both pointers start at the same end but move at different speeds or
      based on different conditions. Used for in-place modifications.
    example: "Remove Duplicates, Move Zeros, Remove Element"

  - name: Sliding window variant
    description: |
      Two pointers defining a window that expands and contracts. Technically
      a separate pattern but uses similar mechanics.
    example: "Minimum Window Substring, Longest Substring Without Repeating"

  - name: Three pointers
    description: |
      Extension with three pointers for problems involving triplets or
      partitioning into three sections.
    example: "3Sum, Sort Colors (Dutch National Flag)"

related_patterns:
  - sliding-window
  - fast-slow-pointers
  - binary-search

prerequisite_patterns: []

visualization_examples:
  - id: "find-pair-sum"
    title: "Find pair with sum = 10"
    input:
      array: [1, 2, 4, 6, 8, 10]
      target: 10
    code: |
      left, right = 0, len(arr) - 1
      while left < right:
          curr = arr[left] + arr[right]
          if curr == target:
              return [left, right]
          elif curr < target:
              left += 1
          else:
              right -= 1
    steps:
      - id: "1"
        description: "Initialize pointers at both ends of the sorted array. Left starts at index 0 (value 1), right starts at index 5 (value 10)."
        structures:
          array:
            type: array
            values:
              - { value: 1, state: active, annotations: ["L"] }
              - { value: 2, state: default }
              - { value: 4, state: default }
              - { value: 6, state: default }
              - { value: 8, state: default }
              - { value: 10, state: active, annotations: ["R"] }
            pointers: { left: 0, right: 5 }
        variables: { left: 0, right: 5, target: 10 }
        codeHighlight: { startLine: 1, endLine: 1 }

      - id: "2"
        description: "Calculate current sum: 1 + 10 = 11. This is greater than target (10), so we need a smaller sum. Move right pointer left."
        structures:
          array:
            type: array
            values:
              - { value: 1, state: comparing, annotations: ["L"] }
              - { value: 2, state: default }
              - { value: 4, state: default }
              - { value: 6, state: default }
              - { value: 8, state: default }
              - { value: 10, state: comparing, annotations: ["R"] }
            pointers: { left: 0, right: 5 }
        variables: { left: 0, right: 5, curr: 11, target: 10 }
        codeHighlight: { startLine: 3, endLine: 8 }

      - id: "3"
        description: "Right pointer moved to index 4 (value 8). Now checking new sum."
        structures:
          array:
            type: array
            values:
              - { value: 1, state: active, annotations: ["L"] }
              - { value: 2, state: default }
              - { value: 4, state: default }
              - { value: 6, state: default }
              - { value: 8, state: active, annotations: ["R"] }
              - { value: 10, state: visited }
            pointers: { left: 0, right: 4 }
        variables: { left: 0, right: 4, target: 10 }
        codeHighlight: { startLine: 8, endLine: 8 }

      - id: "4"
        description: "Calculate current sum: 1 + 8 = 9. This is less than target (10), so we need a larger sum. Move left pointer right."
        structures:
          array:
            type: array
            values:
              - { value: 1, state: comparing, annotations: ["L"] }
              - { value: 2, state: default }
              - { value: 4, state: default }
              - { value: 6, state: default }
              - { value: 8, state: comparing, annotations: ["R"] }
              - { value: 10, state: visited }
            pointers: { left: 0, right: 4 }
        variables: { left: 0, right: 4, curr: 9, target: 10 }
        codeHighlight: { startLine: 3, endLine: 7 }

      - id: "5"
        description: "Left pointer moved to index 1 (value 2). Now checking new sum."
        structures:
          array:
            type: array
            values:
              - { value: 1, state: visited }
              - { value: 2, state: active, annotations: ["L"] }
              - { value: 4, state: default }
              - { value: 6, state: default }
              - { value: 8, state: active, annotations: ["R"] }
              - { value: 10, state: visited }
            pointers: { left: 1, right: 4 }
        variables: { left: 1, right: 4, target: 10 }
        codeHighlight: { startLine: 7, endLine: 7 }

      - id: "6"
        description: "Calculate current sum: 2 + 8 = 10. This equals the target! We found our pair at indices [1, 4]."
        structures:
          array:
            type: array
            values:
              - { value: 1, state: visited }
              - { value: 2, state: found, annotations: ["L"] }
              - { value: 4, state: default }
              - { value: 6, state: default }
              - { value: 8, state: found, annotations: ["R"] }
              - { value: 10, state: visited }
            pointers: { left: 1, right: 4 }
        variables: { left: 1, right: 4, curr: 10, target: 10 }
        codeHighlight: { startLine: 4, endLine: 5 }
