Skip to main content
Scenario Article

Cyclic Sort: Find Missing and Duplicate Numbers in O(n) Time, O(1) Space

Deep Dive: The Cycle Invariant and Why O(n) Holds Despite Nested Loops

Abstract Algorithmsβ€’Mar 29, 2026
XLinkedInReddit
πŸ’‘ Executive Summary

If an array holds n numbers in range [1, n], each number belongs at index num - 1 .

🎯 System Scenario & Requirements

System Goal

If an array holds n numbers in range [1, n], each number belongs at index num - 1 .

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • TLDR: If an array holds n numbers in range [1, n], each number belongs at index num - 1 .
  • Cyclic sort places every element at its correct index in O(n) time using O(1) space β€” then a single scan reveals every missing and duplicate number.
  • Five interview problems, one technique.

Constraints & Assumptions

  • Lesson 1 β€” Forgetting the bounds check.
  • If the problem has out-of-range values (e.g., nums[i] = 0 in a [1, n] array, or nums[i] n ), computing j = nums[i] - 1 will produce a negative or out-of-bounds index.

⚑ Execution Stages & Decision Trace

1

Stage 1 β€” The Missing Number Challenge That Bans the Easy Solution

Action / Decision:

Here is the exact constraint that forces cyclic sort into existence: "Given an array of n integers in the range [1, n], find all missing numbers β€” in O(n) time and O(1) space.

Component Behavior:

You may not use a HashSet or any additional array." Without the O(1) space constraint, this is trivial: drop every number into a HashSet , then scan 1..n for gaps.

Observable Output: That costs O(n) time and O(n) space.
Read Full Stage in Deep Dive ↓
πŸ“–

Deep Dive

Detailed Architectural Analysis

Scenario context

If an array holds n numbers in range [1, n], each number belongs at index num - 1. Cyclic sort places every element at its correct index in O(n) time using O(1) space β€” then a single scan reveals every missing and duplicate number. Five interview problems, one technique.


Under the Hood: Internals of Cyclic Placement

The algorithm looks like it could be O(nΒ²) β€” there is a while loop and each iteration…

This walkthrough focuses on Deep Dive: The Cycle Invariant and Why O(n) Holds Despite Nested Loops.

Before making a design choice, clarify:

  • Under the Hood: Internals of Cyclic Placement The algorithm looks like it could be O(nΒ²) β€” there is a while loop and each iteration may not advance i .
  • But the key insight is that every swap permanently places at least one element at its correct index .
  • Each element can be swapped into its correct position at most once.

Concepts in play: database-indexing, sorting-and-searching-algorithms, jvm-internals-and-garbage-collection, caching

Stage 1 β€” The Missing Number Challenge That Bans the Easy Solution

  • Here is the exact constraint that forces cyclic sort into existence: "Given an array of n integers in the range [1, n], find all missing numbers β€” in O(n) time and O(1) space.
  • You may not use a HashSet or any additional array." Without the O(1) space constraint, this is trivial: drop every number into a HashSet , then scan 1..n for gaps.
  • That costs O(n) time and O(n) space.
  • With the constraint, the HashSet is off the table.

Observable outcome: With the constraint, the HashSet is off the table.

Stage 2 β€” Index as Identity: The Invariant That Drives the Algorithm

  • The invariant is deceptively simple: Number k belongs at index k βˆ’ 1 .
  • For an array [3, 1, 4, 2] (n = 4, range [1, 4]): After cyclic sort, every element sits at its correct index: [1, 2, 3, 4] .
  • No mismatches β€” no missing numbers.
  • For a missing number scenario like [3, 4, -1, 1] (where βˆ’1 is a placeholder for missing 2), the algorithm places what it can and leaves the slot for 2 empty (or containing a wrong value), revealing the gap.

Observable outcome: For a missing number scenario like [3, 4, -1, 1] (where βˆ’1 is a placeholder for missing 2), the algorithm places what it can and leaves the slot for 2 empty (or containing a wrong value), revealing the gap.

Stage 3 β€” The Cyclic Sort Algorithm: Place Every Number at Its Correct Index

  • Walk the array from left to right.
  • At each position i : - Compute the correct index for nums[i] : that is j = nums[i] - 1 .
    • If nums[i] != nums[j] (the number is not already at its correct home), swap nums[i] with nums[j] .
    • If nums[i] == nums[j] (either in place or a duplicate), advance i .
public class CyclicSort {

    // Core cyclic sort β€” places each number at index nums[i] - 1
    public void cyclicSort(int[] nums) {
        int i = 0;
        while (i < nums.length) {
            int j = nums[i] - 1; // correct index for nums[i]
            if (nums[i] != nums[j]) {
                swap(nums, i, j); // send nums[i] to its correct home
            } else {
                i++; // nums[i] is in place (or duplicate) β€” advance
            }
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

Observable outcome: - If nums[i] == nums[j] (either in place or a duplicate), advance i .

Stage 4 β€” Cyclic Placement Step by Step: Visualizing the Swap Cycle

  • After the sort completes, every slot where nums[i] != i + 1 is a mismatch.
  • The mismatch's index tells you which number is missing ( i + 1 ), and the mismatch's value tells you which number is a duplicate ( nums[i] ).
flowchart TD
    A[Start: i = 0] --> B[Compute j = nums[i] - 1]
    B --> C{nums[i] == nums[j]?}
    C -->|"No  wrong position"| D[Swap nums[i] with nums[j]]
    D --> B
    C -->|"Yes  in place or duplicate"| E[Advance: i++]
    E --> F{i < n?}
    F -->|Yes| B
    F -->|No| G[Cyclic sort complete]
    G --> H[Scan array for index-value mismatches]
    H --> I[Report missing / duplicate values]

Observable outcome: The mismatch's index tells you which number is missing ( i + 1 ), and the mismatch's value tells you which number is a duplicate ( nums[i] ).

Stage 5 β€” Real-World Applications: In-Place Array Mutation at Scale

  • Deduplication pipelines in data engineering often process arrays of entity IDs (sequential integers assigned to database rows).
  • When a batch of IDs in range [1, n] arrives and missing IDs indicate failed inserts, cyclic sort's O(1) space is critical β€” the data batch itself can be tens of millions of rows, and allocating an auxiliary array of equ…
  • Embedded systems with severe memory constraints (microcontrollers, IoT firmware) use in-place algorithms where heap allocation is banned.
  • Cyclic sort's zero-allocation pattern is a direct fit.

Observable outcome: Cyclic sort's zero-allocation pattern is a direct fit.

Stage 6 β€” When to Reach for Cyclic Sort

Observable outcome: You can explain how β€œWhen to Reach for Cyclic Sort” changes the architecture or implementation path.

End-to-end outcome

After working through the stages, you should be able to explain:

    • Core invariant : number k belongs at index k βˆ’ 1 .
  • After sorting, mismatches tell the whole story.

Alternatives considered

Cyclic Sort vs HashSet: When Space Actually Matters

The cyclic sort vs HashSet trade-off is not just academic: Cyclic sort is the right choice when: the input can be modified, random access is available (array, not stream), the O(1) space constraint is hard, and you need a single pattern that handles missing numbers, duplicates, and corrupt pairs uniformly.

Failure and degraded modes

Lessons from Cyclic Sort Pitfalls

  • Detection: Lesson 1 β€” Forgetting the bounds check.
  • User or operator impact: Lesson 1 β€” Forgetting the bounds check.
First article in seriesNext Article β†’

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit