Quick Sort in C: Partition Trace and Bounded Stack

Quick sort in C partitions an array around a pivot and sorts the resulting ranges. This teaching implementation uses the last element as a pivot. It recurses on the smaller side and loops over the larger one, limiting call-stack depth without hiding the O(n²) worst-case running time.

Quicksort in C — conceptual cover illustration

Complete program

#include <stdio.h>
#include <stddef.h>

static void swap(int *a, int *b) {
    int temporary = *a; *a = *b; *b = temporary;
}

static size_t partition(int values[], size_t low, size_t high) {
    int pivot = values[high - 1];
    size_t boundary = low;
    for (size_t j = low; j < high - 1; ++j) {
        if (values[j] < pivot) {
            swap(&values[boundary], &values[j]);
            ++boundary;
        }
    }
    swap(&values[boundary], &values[high - 1]);
    return boundary;
}

static void sort_range(int values[], size_t low, size_t high) {
    while (high - low > 1) {
        size_t pivot = partition(values, low, high);
        if (pivot - low < high - (pivot + 1)) {
            sort_range(values, low, pivot);
            low = pivot + 1;
        } else {
            sort_range(values, pivot + 1, high);
            high = pivot;
        }
    }
}

void quick_sort(int values[], size_t count) {
    sort_range(values, 0, count);
}

int main(void) {
    int values[] = {64, 34, 25, 12, 22};
    size_t count = sizeof values / sizeof values[0];
    quick_sort(values, count);
    for (size_t i = 0; i < count; ++i) printf("%s%d", i ? " " : "", values[i]);
    putchar('\n');
    return 0;
}

Run bounded-stack quicksort online

Expected output

Reference output for the displayed program, not a live execution result.

12 22 25 34 64

Trace the first partition

The range is half-open, [low, high). The pivot is 22. Before each scan, values before boundary are smaller than the pivot, and scanned values from boundary onward are not smaller.

Action boundary Array
Start, pivot 22 0 64 34 25 12 22
Scan 64, 34, 25: no swaps 0 64 34 25 12 22
Scan 12: swap with 64 1 12 34 25 64 22
Place pivot at boundary 1 12 22 25 64 34

The pivot at index 1 is now final. Sort [0,1) and [2,5). In the latter range pivot 34 leaves 25 34 64. The finished array is 12 22 25 34 64. Empty and one-element ranges immediately stop; excluding the placed pivot ensures progress even with duplicates.

Complexity, duplicates and stack depth

Balanced partitions give O(n log n) time. Under a random permutation of distinct keys, the usual average analysis is also O(n log n). But sorted, reverse-sorted and all-equal inputs can cause O(n²) work with this last-pivot, two-way partition. Do not label every input logarithmic, or assume duplicates automatically split evenly.

Each recursive call receives at most half the current elements; the larger side is handled by a loop. Therefore simultaneous recursion depth is O(log n), rather than the O(n) stack growth of naive two-sided recursion. Partitioning itself uses O(1) extra storage. This does not eliminate the quadratic number of comparisons. Randomized pivots reduce the chance of bad splits; three-way partitioning helps repeated keys. Neither is silently implemented here.

This version is not stable. With labeled keys 2a 2b 1, the pivot swap can produce 1 2b 2a, reversing equal records. The labels illustrate identity; they are not integer literals. Choose merge sort when stable merging is required, or heap sort for in-place worst-case O(n log n) time.

Array contract and common mistakes

The function modifies count writable integers. A null pointer with count zero is accepted; positive counts require a valid array. It does not allocate memory or read stdin. Half-open bounds avoid subtracting one from an unsigned zero index. The pivot is read only when the range contains at least two elements. Check all-equal, sorted, reverse, negative and duplicate values, plus INT_MIN and INT_MAX.

Do not recurse over a range that includes the placed pivot unchanged: that can prevent termination. Do not use integer subtraction as a comparator when values may span the full integer range. Test a small array using insertion sort to contrast shifting with partitioning.

Practice and answer

Trace 3 1 3 2: pivot 2 leaves 1 2 3 3, with pivot index 1. Values equal to the pivot are not moved into the smaller-than region by this strict comparison. For an all-equal array every pivot lands at the beginning of its current range, so work stays quadratic while recursive stack usage remains bounded. See the sorting table for trade-offs.

Related examples