Learn coding concepts by building real projects with modern technologies

Quick Sort in Golang

By Lane Wagner on Jun 17, 2021

Quicksort is an efficient sorting algorithm commonly used in production sorting implementations. Like Merge Sort, Quicksort is a divide-and-conquer algorithm. As the name implies, Quicksort is one of the fastest sorting algorithms, but you have to pay attention to detail in your implementation because if you’re not careful, your speed can drop quickly.

Divide

Learn Go by writing Go code

I'm a senior engineer learning Go, and the pace of Boot.dev's Go Mastery courses has been perfect for me. The diverse community in Discord makes the weekly workshops a blast, and other members are quick to help out with detailed answers and explanations.

- Daniel Gerep from Cassia, Brasil

Conquer

Quicksort Pseudocode

Partition() function in Golang

Quicksort actually makes use of two functions, the main quicksort() function as well as the partition() function. The meat of the algorithm counter-intuitively lives in the partition() function. It’s responsible for finding the pivot and moving everything to the correct side of the pivot.

In Go, the complete code would look like this.

func partition(arr []int, low, high int) ([]int, int) {
	pivot := arr[high]
	i := low
	for j := low; j < high; j++ {
		if arr[j] < pivot {
			arr[i], arr[j] = arr[j], arr[i]
			i++
		}
	}
	arr[i], arr[high] = arr[high], arr[i]
	return arr, i
}

QuickSort() function in Golang

The quickSort() function is really just a wrapper around the partition function, and it handles the recursive nature of the algorithm.

func quickSort(arr []int, low, high int) []int {
	if low < high {
		var p int
		arr, p = partition(arr, low, high)
		arr = quickSort(arr, low, p-1)
		arr = quickSort(arr, p+1, high)
	}
	return arr
}

Example of using Quicksort in real code

fmt.Println(quickSortStart([]int{5, 6, 7, 2, 1, 0))
// prints
// [0, 1, 2, 5, 6, 7]

Why use Quicksort?

On average, quicksort has a Big O of O(n*log(n)). In the worst case, and assuming we don’t take any steps to protect ourselves, it can break down to O(n^2). The partition() function has a single for-loop that ranges from the lowest index to the highest index in the array. By itself, the partition() function is O(n). The overall complexity of quicksort is dependent on how many times partition() is called.

In the worst case, the input is already sorted. An already sorted array results in the pivot being the largest or smallest element in the partition each time. When this is the case, partition() is called a total of n times. In the best case, the pivot is the middle element of each sublist which results in log(n) calls to partition().

Quick sort has the following properties.

Ensuring a fast runtime in Quicksort

While the version of quicksort that we implemented is almost always able to perform at speeds of O(n*log(n)), it’s Big O complexity is still technically O(n^2). We can fix this by altering the algorithm slightly. There are two approaches:

Random shuffling optimization

The random approach is easy to code, works practically all of the time, and as such is often used. The idea is to quickly shuffle the list before sorting it. The likelihood of shuffling into a sorted list is astronomically unlikely, and is also more unlikely the larger the input.

Finding the median optimization

One of the most popular solutions is to use the “median of three” approach. Three elements (for example: the first, middle, and last elements) of each partition are chosen and the median is found between them. That item is then used as the pivot. This approach has the advantage that it can’t break down to O(n^2) time because we are guaranteed to never use the worst item in the partition as the pivot. That said, it can still be slow_er_ because a true median isn’t used.

Learn to code by building real projects

Related Reading