Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 2 – Arrays, strings and in-place techniques

In-place techniques: reverse, rotate, partition

Rearrange an array with O(1) extra memory: reverse with two indices, rotate by three reversals, filter with read and write indices, and partition.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 9 of 25 minutes free
Free: Reversing with two indices · Rotating by three reversals · Keeping what you need: read and write indices · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 16 minutes.

What you will learn

  • Rotate an array in place by three reversals, with k reduced modulo n
  • Partition an array around a pivot and into three bands
  • Remove duplicates from a sorted array with read and write indices
  • Prove each technique correct with a loop invariant

Before you start

An in-place technique rearranges an array inside the array itself, using only a few index variables: O(1) extra memory, no second array. Three building blocks cover most of what you need. Two indices moving towards each other reverse an array in n/2 swaps. Three reversals rotate it: to move every item k places to the right, reverse the whole array, then the first k items, then the rest. Read and write indices filter it: the read index visits every item, the write index marks where the next kept item goes, so removing duplicates, dropping a value or moving items below a pivot to the front all take one pass. Each runs in O(n) time. The price of working in place is that the input is gone once the work is done, and that some methods, partitioning in particular, do not keep the order of equal items.

Reversing with two indices

Put one index at each end, swap the two items, and move both inwards until they meet. Every swap fixes two positions for good, so an array of n items needs ⌊n/2⌋ swaps and two index variables, whatever n is. Python’s list.reverse() works this way; items[::-1] instead builds a second list of n references, which is why it is not in place.

Rotating by three reversals

Rotating right by k moves every item k places to the right and wraps the last k round to the front. Rotating by n changes nothing, so first reduce k to k mod n: rotating 7 items by 10 is the same as rotating them by 3. Then:

  1. Reverse the whole array: the last k items arrive at the front, but backwards.
  2. Reverse the first k items, which puts them in order.
  3. Reverse the remaining n − k items, which puts them in order too.

The program below traces both rotations of [1..7], by 3 and by 10, with the same three steps:

Reverse, rotate, remove duplicates and partition, in place Python · inplace/inplace.py
"""Four in-place techniques on Python lists: each uses a few index variables and never builds a second list."""


def reverse(a, lo=0, hi=None):
    """Reverses a[lo:hi] in place with two indices that move towards each other."""
    i, j = lo, (len(a) if hi is None else hi) - 1
    while i < j:
        a[i], a[j] = a[j], a[i]
        i += 1
        j -= 1


def rotate_right(a, k):
    """Moves every item k places to the right, wrapping around: three reversals."""
    n = len(a)
    if n == 0:
        return
    k %= n  # rotating by n changes nothing, so only k mod n counts
    reverse(a)
    reverse(a, 0, k)
    reverse(a, k, n)


def remove_duplicates(a):
    """Keeps the first copy of each value of a sorted list, in order; returns how many items are kept."""
    write = 0
    for read in range(len(a)):
        # Invariant: a[0:write] holds the distinct values of a[0:read], in order.
        if write == 0 or a[read] != a[write - 1]:
            a[write] = a[read]
            write += 1
    return write


def partition(a, pivot):
    """Moves every item below pivot to the front; returns where the rest begins. The order is not kept."""
    write = 0
    for read in range(len(a)):
        if a[read] < pivot:
            a[write], a[read] = a[read], a[write]
            write += 1
    return write


for k in (3, 10):
    a = list(range(1, 8))
    m = k % len(a)
    print(f"rotate [1..7] right by {k} (k mod 7 = {m}):")
    reverse(a)
    print("  reverse all          ", a)
    reverse(a, 0, m)
    print(f"  reverse the first {m}  ", a)
    reverse(a, m, 7)
    print(f"  reverse the last {7 - m}   ", a)
    b = list(range(1, 8))
    rotate_right(b, k)
    assert a == b

marks = [40, 40, 55, 55, 55, 61, 78, 78, 90]
kept = remove_duplicates(marks)
print(f"remove_duplicates kept {kept}: {marks[:kept]}  (the rest of the list: {marks[kept:]})")

scores = [72, 35, 90, 18, 64, 41, 77, 29]
start = partition(scores, 50)
print(f"partition around 50: below {scores[:start]}, the rest {scores[start:]}")

Output

rotate [1..7] right by 3 (k mod 7 = 3):
  reverse all           [7, 6, 5, 4, 3, 2, 1]
  reverse the first 3   [5, 6, 7, 4, 3, 2, 1]
  reverse the last 4    [5, 6, 7, 1, 2, 3, 4]
rotate [1..7] right by 10 (k mod 7 = 3):
  reverse all           [7, 6, 5, 4, 3, 2, 1]
  reverse the first 3   [5, 6, 7, 4, 3, 2, 1]
  reverse the last 4    [5, 6, 7, 1, 2, 3, 4]
remove_duplicates kept 5: [40, 55, 61, 78, 90]  (the rest of the list: [61, 78, 78, 90])
partition around 50: below [35, 18, 41, 29], the rest [64, 90, 77, 72]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 inplace.py

Each step reverses part of the array in place, so the rotation needs no extra memory and about n swaps in all. Copying into a new array would take one write per item but n extra slots. A third way, the juggling or cycle method, moves every item straight to its final place, one cycle of positions at a time.

Keeping what you need: read and write indices

The read index looks at every item once; the write index marks where the next item worth keeping goes. Whatever is kept is copied to the write position, and the write index moves on. For remove_duplicates on a sorted list an item is kept when it differs from the last kept one, and the method rests on one invariant: a[0:write] holds the distinct values of a[0:read], in order.

A sorted list mid-run: the first three slots hold the kept values 40, 55 and 61; the read index has reached position 6.marks = [40, 40, 55, 55, 55, 61, 78, 78, 90]remove_duplicates, partway throughwrite = 3, read = 6Invariant: marks[0:write] holds the distinct values of marks[0:read], in order.marks[6] = 78 is new (the last kept value is 61), so it goes to position write = 3.0: 40kept1: 55kept2: 61kept3: 55read, free to overwrite4: 55read, free to overwrite5: 61read, free to overwrite6: 78next to read7: 78not read yet8: 90not read yet

Read and write indices keep a prefix that is already the answer

Text description of the diagram

The diagram shows remove_duplicates partway through marks = [40, 40, 55, 55, 55, 61, 78, 78, 90], with write = 3 and read = 6.

  • Positions 0 to 2 hold 40, 55 and 61: the distinct values found so far, in order. This prefix, marks[0:write], is the answer so far.
  • Positions 3 to 5 have been read already. Their old values no longer matter, so the method may overwrite them.
  • Position 6, holding 78, is the next to read; positions 7 and 8 have not been read yet.
  • The invariant is that marks[0:write] holds the distinct values of marks[0:read], in order. Because 78 differs from the last kept value, 61, it is copied to position 3 and write becomes 4. When read reaches the end, marks[0:write] holds every distinct value.

In the run, the nine marks keep their five distinct values at the front, and the slots behind the write index still hold leftovers, which the caller ignores or removes. The same two indices remove every copy of one value, move the zeros to the end, or keep only the items that pass any test, always in one pass and always in order. When the order does not matter, a removed item’s slot can instead be filled with the last item.

Partitioning around a pivot

Partitioning moves the items below a pivot to the front: when the read index finds a small item, it swaps it with the item at the write index. In the run, 35, 18, 41 and 29 arrive at the front in their original order, while 72, 90, 64 and 77 are shuffled to 64, 90, 77 and 72. That is the general rule: an in-place partition is not stable, so equal items, or items on the same side, may not keep their order. A three-way partition, also called the Dutch national flag method, splits an array into items below, equal to and above a pivot in one pass with three indices. Quicksort is built on partitioning, and the three-way version keeps it fast on inputs with many equal keys.

Exercise

Move every 0 to the end of a list in place, keeping the order of the other numbers: one read index, one write index.

Exercise · Easy · Python, JavaScript

Move the zeros to the end, in place and in order

A shop's till records how many items each customer bought, and a 0 marks a cancelled sale. Write move_zeros(counts) (JavaScript: moveZeros(counts)), which moves every 0 to the end of the list in place, keeping the other numbers in their original order. [0, 3, 0, 1, 4] becomes [3, 1, 4, 0, 0].

  • Change the list you are given and return nothing: do not build a second list, and do not sort.
  • The numbers other than 0 keep their relative order, and the list keeps its length.
  • An empty list, a list without zeros and a list of zeros are all valid input.

The sample tests check the results, that the same list object was changed, and, in Python, that the extra memory the function needs stays under 20,000 bytes on a list of 100,000 numbers; a second list would need about 800,000.

Python · Starter code · move_zeros.py

def move_zeros(counts):
    """Moves every 0 in counts to the end, in place, keeping the order of the other numbers."""
    # Replace this line with your code.
    raise NotImplementedError("write move_zeros")
The sample tests · test_move_zeros.py
import tracemalloc

from move_zeros import move_zeros


def moved(values):
    counts = list(values)
    assert move_zeros(counts) is None
    return counts


def test_example():
    """[0, 3, 0, 1, 4] becomes [3, 1, 4, 0, 0]"""
    assert moved([0, 3, 0, 1, 4]) == [3, 1, 4, 0, 0]


def test_order_kept():
    """the other numbers keep their order, repeated values included"""
    assert moved([5, 0, 5, 2, 0, 0, 2, 9]) == [5, 5, 2, 2, 9, 0, 0, 0]


def test_edge_cases():
    """an empty list, no zeros and only zeros all work"""
    assert moved([]) == []
    assert moved([1, 2, 3]) == [1, 2, 3]
    assert moved([0, 0, 0]) == [0, 0, 0]
    assert moved([0]) == [0]


def test_same_list_changed():
    """the function changes the list it was given"""
    counts = [0, 1]
    alias = counts
    move_zeros(counts)
    assert alias == [1, 0] and alias is counts


def test_no_second_list():
    """on 100,000 numbers the extra memory peak stays under 20,000 bytes"""
    counts = [k % 7 for k in range(100_000)]
    expected = [k % 7 for k in range(100_000) if k % 7] + [0] * (100_000 // 7 + 1)
    tracemalloc.start()
    try:
        move_zeros(counts)
        peak = tracemalloc.get_traced_memory()[1]
    finally:
        tracemalloc.stop()
    assert counts == expected
    assert peak < 20_000, f"peak extra memory was {peak:,} bytes"

JavaScript · Starter code · move_zeros.mjs

/** Moves every 0 in counts to the end, in place, keeping the order of the other numbers. */
export function moveZeros(counts) {
  // Replace this line with your code.
  throw new Error('write moveZeros');
}
The sample tests · move_zeros.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { moveZeros } from './move_zeros.mjs';

function moved(values) {
  const counts = [...values];
  assert.equal(moveZeros(counts), undefined);
  return counts;
}

test('[0, 3, 0, 1, 4] becomes [3, 1, 4, 0, 0]', () => assert.deepEqual(moved([0, 3, 0, 1, 4]), [3, 1, 4, 0, 0]));

test('the other numbers keep their order, repeated values included', () =>
  assert.deepEqual(moved([5, 0, 5, 2, 0, 0, 2, 9]), [5, 5, 2, 2, 9, 0, 0, 0]));

test('an empty list, no zeros and only zeros all work', () => {
  assert.deepEqual(moved([]), []);
  assert.deepEqual(moved([1, 2, 3]), [1, 2, 3]);
  assert.deepEqual(moved([0, 0, 0]), [0, 0, 0]);
  assert.deepEqual(moved([0]), [0]);
});

test('the function changes the array it was given', () => {
  const counts = [0, 1];
  const alias = counts;
  moveZeros(counts);
  assert.ok(alias === counts);
  assert.deepEqual(alias, [1, 0]);
});

test('100,000 numbers come out right', () => {
  const counts = Array.from({ length: 100000 }, (_, k) => k % 7);
  const kept = counts.filter((x) => x !== 0);
  moveZeros(counts);
  assert.equal(counts.length, 100000);
  assert.deepEqual(counts.slice(0, kept.length), kept);
  assert.ok(counts.slice(kept.length).every((x) => x === 0));
});
A hint

Use a read index that visits every position and a write index that marks where the next non-zero number goes. Copy each non-zero number to the write position and move the write index on; when the read index reaches the end, every position from the write index onwards should hold 0.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What is [1, 2, 3, 4, 5] rotated right by 2?

    Choose one answer.

    Show the answer to question 1

    Answer: [4, 5, 1, 2, 3]

    Every item moves 2 places to the right and the last two wrap round to the front. By three reversals: [5, 4, 3, 2, 1], then the first 2 reversed gives [4, 5, 3, 2, 1], then the last 3 reversed gives [4, 5, 1, 2, 3].

  2. Question 2 of 5 An array of 7 items is rotated right by 1,000. By how many places does it actually rotate?

    Type a number.

    Show the answer to question 2

    Answer: 6

    Rotating by 7 brings every item back to where it started, so only 1,000 mod 7 counts: 1,000 = 142 × 7 + 6. That is why the rotation reduces k modulo n before it reverses anything.

  3. Question 3 of 5 In remove_duplicates, what is true of a[0:write] at the start of every pass of the loop?

    Choose one answer.

    Show the answer to question 3

    Answer: It holds the distinct values of a[0:read], in order

    That invariant is the whole method: it holds before the loop, when both are empty, each pass keeps it, and when read reaches the end it says that a[0:write] holds every distinct value of the list.

  4. Question 4 of 5 Which of these reverses a Python list of n items in place, with O(1) extra memory?

    Choose one answer.

    Show the answer to question 4

    Answer: items.reverse()

    items.reverse() swaps pairs inside the list itself. The slice and list(reversed(…)) build a second list of n references and then rebind the name; sorted builds a new list too, and sorts instead of reversing.

  5. Question 5 of 5 partition([72, 35, 90, 18, 64, 41, 77, 29], 50) puts the numbers below 50 first. In the recorded run, what order did the numbers of 50 and above end up in?

    Choose one answer.

    Show the answer to question 5

    Answer: 64, 90, 77, 72: not their original order

    Each swap moves a large number to wherever the next small number was found, so the large ones end up shuffled. An in-place partition is not stable: it does not keep the order of the items it does not move to the front.

Why each technique is correct: invariants, proofs and three bands Pro: opens with a Pro, Premium or Ultimate pass

Each technique rests on one invariant, and stating it is most of the proof. This part proves the rotation identity and the read and write invariant, runs a three-way partition that checks its invariant after every step, and reproduces the loss of order that in-place partitioning causes.

In this part:

  • The three-reversal identity, proved
  • The read and write invariant, proved in three steps
  • Three bands, with the invariant checked after every step
  • Order lost, reproduced, and two ways to keep it

2 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Rotate and filter in five languages, and the modulo trap Pro: opens with a Pro, Premium or Ultimate pass

The techniques are the same in every language; what differs is what the remainder operator does with a negative or a zero divisor. This part runs the lesson’s code in JavaScript, Java, C++ and Go, then reproduces the modulo trap in all five languages.

In this part:

  • Reverse, rotate, remove duplicates and partition in four more languages
  • The modulo trap, reproduced in five languages
  • Seven inputs every rotation must pass

4 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Variants with numbers: writes, cache behaviour and memory Pro: opens with a Pro, Premium or Ultimate pass

Two in-place methods that do the same job can differ by a factor in writes, in speed and in what they preserve. This part counts the writes of two rotations and two removals, times the rotations in C++, and measures the memory that in-place work saves.

In this part:

  • Three reversals against the juggling rotation: writes counted
  • Fewer writes, slower: the rotations timed in C++
  • Removing a rare value: a thousand writes or a million
  • The memory that working in place saves, measured

4 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher · Warm-up

    What does "in place" mean for an array algorithm, and is reversing with items[::-1] in place?

    An in-place algorithm rearranges the array inside the array itself and needs only O(1) extra memory, a few index variables, however long the array is; some definitions also allow O(log n) for a recursion stack. items[::-1] is not in place: it builds a second list of n references, O(n) extra memory, and assigning it back to items only rebinds the name. items.reverse() is in place: it swaps pairs from the two ends inwards, ⌊n/2⌋ swaps, inside the same list. The difference matters when the array is large compared with the memory available, and when other code holds a reference to the same list and expects to see the change.

  2. Mid to senior

    An array of 100 million 4-byte integers must be rotated right by k, with at most 50 MB of memory beyond the array. Compare copying into a new array, three reversals and the cycle method by memory, writes and speed, and choose one.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    A report groups student records by grade with an in-place three-way partition. Staff complain that students with the same grade no longer appear in roll-number order, as they did before. Explain why, and give two fixes with their time and memory.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A device keeps 10 million readings in flash memory, where a write costs about 100 times a read. About 0.1 % of the readings are invalid and must be removed in place. Compare two methods by the number of writes, and say when the cheaper one is not allowed.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • In place means O(1) extra memory: a few indices, no second array.
  • Reverse with two indices moving inwards; rotate right by k with three reversals, after reducing k modulo n.
  • Read and write indices filter in one pass and keep order: a[0:write] is always the answer so far.
  • An in-place partition is not stable; a three-way partition splits items into below, equal and above in one pass.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.