Three pointers partition the array into 0s, 1s and 2s in a single pass: low and high grow inward while mid scans.
Sort an array of 0s, 1s and 2s in a single pass, in place, with no counting array. Three pointers carve the array into four regions: `low` seals the 0s on the left, `high` seals the 2s on the right, and `mid` walks forward through whatever is still unknown between them. The loop ends the moment the unknown region is empty.
At every moment: [0, low) is all 0s, [low, mid) is all 1s, [mid, high] is unexamined, and (high, n-1] is all 2s. Every branch of the loop is chosen to keep that sentence true. The algorithm is finished exactly when the unknown region closes (mid > high).
On a 0, the value is swapped down to `low` and BOTH low and mid advance. That is safe, because the slot at `low` held a 1, which is already known and belongs where mid was. On a 1, only mid advances. But on a 2, the value is swapped out to `high`, high shrinks, and mid STAYS. The value that just arrived from the right end has never been examined; advancing over it would leave an unsorted value behind forever.
Six values, five iterations: one index gets visited twice because a 2-swap holds mid in place. Watch row 1: mid stays at 0 while high drops from 5 to 4, and the very next row re-examines the same slot.
Each iteration either advances mid or shrinks high, so the size of the unknown region [mid, high] falls by exactly one every time, even on the iterations where mid is held. That is the termination argument, and it is also why the pass is O(n) despite mid sometimes standing still.
The obvious alternative (count the 0s, 1s and 2s, then overwrite the array) is also O(n) time and O(1) space, but it needs two passes and cannot be adapted to a stream where each element may only be touched once. The three-way partition generalises far beyond three colors: it is exactly the partition step of 3-way quicksort, which is what makes quicksort fast on arrays full of duplicate keys.