Arun Pandian M

Arun Pandian M

Android Dev | Full-Stack & AI Learner

In-Place Slow–Fast Scan Pattern: Remove Duplicates from a Sorted Array

The in-place Slow–Fast Scan pattern is designed for problems where data must be processed or cleaned without allocating extra memory. By separating reading and writing responsibilities between two forward-moving pointers, this pattern enables efficient in-place transformations. We’ll explore it through a sorted-array example.

Pattern Definition

The in-place Slow–Fast Scan pattern is used when you want to filter or clean data directly inside an array, without using extra memory.

The idea is simple:

  • The fast pointer scans every element.
  • The slow pointer keeps track of where the next valid element should go.
  • Both pointers move forward, but they serve different purposes.

    This separation of responsibility lets us modify the array in place, efficiently.

    Problem

    You are given a sorted array of integers.

    Your task is to remove duplicate elements in place so that each unique element appears only once.

    You must return the new length of the array that contains only unique values.

    Only the first k elements (where k is the returned length) are considered valid.

    Input

    nums = [1, 1, 2, 2, 3, 3, 4, 4, 5]

    Output

    Updated array (first part): [1, 2, 3, 4, 5]
    New length: 5

    Why the Slow–Fast Scan Works Here

    The array is sorted, which means:

  • Duplicate values always appear next to each other
  • You don’t need a set or hashmap to track what you’ve seen
  • A single pass is enough
  • The slow pointer always points to the last confirmed unique value,

    while the fast pointer checks the next values and decides whether they’re new or duplicates.

    Step-by-Step Intuition

    https://storage.googleapis.com/lambdabricks-cd393.firebasestorage.app/fast_slow_scan.svg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=firebase-adminsdk-fbsvc%40lambdabricks-cd393.iam.gserviceaccount.com%2F20260225%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260225T032813Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&X-Goog-Signature=759d691287f153528d65439637b50a0da9ef60e2fece0846924b36506d0599376edadceaec44b7bd8d8020860c03f2f40f07a144299e267db539093d99a30e37f75d2eecef543f2d48e5b7dbe323b63dd4ab5ce9ea7ade35f0d2730a208b51c8ba303ad478b09002f65805177aa35a1834cad0be3ce77c31d0ec2443a62ed338f5b0fbb79ec06551dc15371dae6a40593a33120a9a9eb8d8ff8825252793cc85a99f2fce02ee885d6370b6c9c6ca6e30f844f8e4e4ec8df665b9caa29a74053de1100845830711e23f386373d17477deb07f33aba03feced8fbfc7068a4ecf458103efe3541bb3ec0b3050e448f97e8792519b5592d01ea89ec19e564f29ab05

    Before we walk through the array, let’s be clear about the roles:

  • The fast pointer moves forward through every value in the array.
  • The slow pointer marks the position where the next unique value should be placed.
  • Now let’s look at the input:

    [1, 1, 2, 2, 3, 3, 4, 4, 5]
  • We start by placing the first value (1) at the beginning. The slow pointer stays here because the first value is always unique.
  • The fast pointer moves to the next value, which is again 1. Since this value is the same as the one already placed, we do nothing and move forward
  • When the fast pointer reaches 2, the value changes. This means we’ve found a new unique value. 1) We move the slow pointer forward. 2) We write 2 at the slow pointer’s position.
  • The next value is another 2. Since it’s a duplicate, the fast pointer moves on without changing anything.
  • Each time the fast pointer encounters a new value (3, then 4, then 5), the slow pointer advances and records it.
  • Any repeated values are simply skipped.
  • By the time the fast pointer reaches the end:

  • The slow pointer has compacted all unique values at the start of the array
  • Everything after that point can be ignored
  • Kotlin Implementation

    fun removeDuplicates(nums: IntArray): Int {
        if (nums.size < 2) return nums.size
    
        var slow = 0
    
        for (fast in 1 until nums.size) {
            if (nums[slow] != nums[fast]) {
                slow++
                nums[slow] = nums[fast]
            }
        }
        return slow + 1
    }
    
    fun main() {
        val nums = intArrayOf(1, 1, 2, 2, 3, 3, 4, 4, 5)
        val newLength = removeDuplicates(nums)
        val result = nums.sliceArray(0 until newLength)
        println(result.joinToString())
    }

    Flow (Step-by-Step)

    Start
      |
      v
    If array size < 2:
      return size
      |
      v
    Initialize slow = 0
      |
      v
    Move fast from index 1 to end:
      |
      v
    If nums[fast] != nums[slow]:
      slow++
      nums[slow] = nums[fast]
      |
      v
    Continue until fast reaches end
      |
      v
    Return slow + 1

    Real-Time Analogy

    Imagine cleaning a sorted attendance list:

    You read names one by one:

  • If the name is the same as the previous one → ignore it.
  • If it’s different → write it down.
  • You don’t rewrite the whole list.

    You simply compress it, keeping only the first occurrence of each name.

    That’s exactly what the slow–fast scan does.

    Real-World Use Cases

    This pattern appears frequently in real systems:

  • Deduplicating sorted database query results
  • Cleaning telemetry or log data
  • Removing duplicate IDs from pre-processed datasets
  • Compacting sorted event streams before analytics
  • Memory-efficient preprocessing in mobile or embedded systems
  • Time & Space Complexity

    Time Complexity: O(n)

    The array is scanned exactly once. The fast pointer moves through every element, while the slow pointer advances only when a new unique value is found. Since both pointers move forward and never step back, the total work is linear.

    Space Complexity: O(1)

    The algorithm modifies the array in place and does not allocate any additional data structures. Apart from the two pointers, no extra memory is used, making it space-efficient.

    Conclusion

    The Remove Duplicates from Sorted Array problem is one of the cleanest examples of the in-place Slow–Fast Scan pattern. It shows how separating reading and writing responsibilities between two pointers allows us to clean data efficiently without using extra memory.

    Once this idea clicks, many similar problems start to look familiar — only the condition for what counts as a “valid” element changes.

    #BuildInPublic#LearningInPublic#AlgorithmPatterns#ScanPatterns#SlowFastScan#InPlaceAlgorithms#ArrayAlgorithms#TwoPointerScan#ProblemSolving#AlgorithmIntuition#SpaceOptimization#TimeComplexity#DSAWithKotlin