Lomuto partition:

def lomuto(arr, low, high):
    
    # choose last element as pivot
    pivot = arr[high] 
    
    # pointer i tracks where to place pivot
    i = low

    # pointer j iterates through array
    for j in range(low, high):
        
        # If current element is smaller than pivot
        if (arr[j] < pivot):
            
            # swap array[i] and array[j]
            array[i], array[j] = array[i], array[j]
            i += 1
    # swap pivot and array[i]
    arr[i], arr[high] = arr[high], arr[i]
    return (i)

Hoare’s partition:

def hoares(arr, low, high):

    # choose last element as pivot
    pivot = arr[high]
    a = low
    b = high - 1

    while True:

        # Move a right until value > pivot
        while a <= b and (arr[a] < pivot):
            a += 1

        # Move b left until value < pivot
        while a <= b and (arr[b] > pivot):
            b -= 1
        if (a < b):
            # Swap values
            arr[a], arr[b] = arr[b], arr[a]
        else:
            # Break out if pointers met
            break
    # Swap pointer with pivot
    arr[a], arr[high] = arr[high], arr[a]
    return a

Quicksort algorithm

def quicksort(arr, left, right)
    if (left < right):
        pivot = partition(arr, left, right)
        quicksort(arr, left, pivot-1)
        quicksort(arr, pivot+1, right)

Comparison:

Hoare’s better performance and efficiency than Lomuto.

But Lomuto’s uses a single pointer compared to Hoare’s double pointer, so Loumto’s is simpler.

It may be better to use Lomuto’s if Hoare’s has insignificant efficiency gain.

Lomuto’s Hoare’s
Efficiency Less efficient Degrades quicksort to O(N2) when array is already sorted. More efficient, does three times less swaps on average. Degrades quicksort to O(N2) when array is already sorted.
Implementation One pointer, simpler to implement Two pointers
Best case Average case Worst case
O(N log N) Each partition divides array into two equal parts. There are then log2N calls to reach subarray of size 1. Each level of call needs O(N) time as each subarray processes a different part of the array. Thus total time O(N log N). O(N log N) Each partition function takes O(N) time. Approximately divides array into half each time, O(log N) levels. O(N2) When array is partitioned into sizes (N-1) and 1. May occur when pivot is repeatedly the smallest or largest element. There are (N-1) calls, and O(N) time each call. So total time is O(N2).