25% off

Use code FUNCMAIN at checkout to get 25% off all premium courses.

Get started →

Video:

Implementing Heap Sort in Go

March 7, 2026

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now.

Twitter: @Elliot_f

👋 Welcome Gophers! In this lesson, we are going to implement the heap sort algorithm that we looked at in the previous lesson.

Implementation

Let’s start by creating a new file called main.go in a directory called 04-heap-sort/. We’ll build up the heap sort implementation step by step.

Let’s start with the basic setup:

package main

import "fmt"

func main() {
  fmt.Println("Heap Sort Algorithm in Go")
}

Now, let’s define the function signatures we’ll need:

// HeapSort - sorts the input slice using the heap sort algorithm
func HeapSort(input []int) {

}

// Heapify - maintains the max-heap property for a subtree rooted at index i
func Heapify(input []int, n int, i int) {

}

Let’s start with the Heapify function. This is the core operation that maintains the max-heap property:

func Heapify(input []int, n int, i int) {
  largest := i
  left := 2*i + 1   // left child
  right := 2*i + 2  // right child

  // if left child is larger than root
  if left < n && input[left] > input[largest] {
    largest = left
  }

  // if right child is larger than largest so far
  if right < n && input[right] > input[largest] {
    largest = right
  }

  // if largest is not root, swap and heapify recursively
  if largest != i {
    input[i], input[largest] = input[largest], input[i]
    Heapify(input, n, largest)
  }
}

The Heapify function works by:

  1. Finding the largest element among the node and its children
  2. If the largest is not the current node, swap them
  3. Recursively heapify the affected subtree

Now let’s implement the main HeapSort function:

func HeapSort(input []int) {
  n := len(input)

  // build max heap
  // we start from the last non-leaf node which is at index (n/2 - 1)
  for i := n/2 - 1; i >= 0; i-- {
    Heapify(input, n, i)
  }

  // extract elements from heap one by one
  for i := n - 1; i > 0; i-- {
    // move current root (maximum) to the end
    input[0], input[i] = input[i], input[0]
    // heapify the reduced heap
    Heapify(input, i, 0)
  }
}

The HeapSort function has two phases:

  1. Build phase: Convert the array into a max-heap by calling Heapify for all non-leaf nodes
  2. Extraction phase: Repeatedly move the maximum element (at root) to the end and heapify the remaining heap

Now let’s test it out by creating a slice in our main function and calling our new HeapSort function:

func main() {
  fmt.Println("Heap Sort Algorithm in Go")

  unsortedInput := []int{5, 3, 4, 1, 2}
  fmt.Println("Before:", unsortedInput)

  HeapSort(unsortedInput)

  fmt.Println("After:", unsortedInput)
}

Perfect! Let’s run this from the command line:

$ go run main.go
Heap Sort Algorithm in Go
Before: [5 3 4 1 2]
After: [1 2 3 4 5]

Excellent! Our heap sort is working correctly. Let’s add a version with debug output to see what’s happening:

func HeapSortDebug(input []int) {
  n := len(input)

  fmt.Println("Building max heap...")
  for i := n/2 - 1; i >= 0; i-- {
    HeapifyDebug(input, n, i, 0)
  }
  fmt.Println("After building heap:", input)

  fmt.Println("\nExtracting elements...")
  for i := n - 1; i > 0; i-- {
    fmt.Printf("Moving %d to final position\n", input[0])
    input[0], input[i] = input[i], input[0]
    HeapifyDebug(input, i, 0, 0)
    fmt.Printf("Heap state: %v\n", input[:i])
  }
}

func HeapifyDebug(input []int, n int, i int, depth int) {
  indent := ""
  for j := 0; j < depth; j++ {
    indent += "  "
  }

  largest := i
  left := 2*i + 1
  right := 2*i + 2

  if left < n && input[left] > input[largest] {
    largest = left
  }

  if right < n && input[right] > input[largest] {
    largest = right
  }

  if largest != i {
    fmt.Printf("%sSwapping %d and %d\n", indent, input[i], input[largest])
    input[i], input[largest] = input[largest], input[i]
    HeapifyDebug(input, n, largest, depth+1)
  }
}

When we run this with debug output, we can see the heapification process:

$ go run main.go
Heap Sort Algorithm in Go
Building max heap...
  Swapping 1 and 5
Swapping 3 and 4
After building heap: [5 3 4 1 2]

Extracting elements...
Moving 5 to final position
Swapping 2 and 3
Heap state: [4 3 2]
Moving 4 to final position
Swapping 2 and 3
Heap state: [3 2]
Moving 3 to final position
Heap state: [2]
After: [1 2 3 4 5]

Perfect! You can see how the algorithm builds the heap and then extracts elements one by one.

Testing

Let’s create a proper test to validate our implementation:

func TestHeapSort(t *testing.T) {
  tests := []struct {
    input    []int
    expected []int
  }{
    {[]int{5, 3, 4, 1, 2}, []int{1, 2, 3, 4, 5}},
    {[]int{1}, []int{1}},
    {[]int{}, []int{}},
    {[]int{2, 1}, []int{1, 2}},
    {[]int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}},
    {[]int{5, 4, 3, 2, 1}, []int{1, 2, 3, 4, 5}},
    {[]int{9, 5, 6, 2, 3, 7, 1, 4, 8}, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}},
  }

  for _, tt := range tests {
    input := make([]int, len(tt.input))
    copy(input, tt.input)
    HeapSort(input)
    if !slicesEqual(input, tt.expected) {
      t.Errorf("HeapSort(%v) = %v, want %v", tt.input, input, tt.expected)
    }
  }
}

func slicesEqual(a, b []int) bool {
  if len(a) != len(b) {
    return false
  }
  for i := range a {
    if a[i] != b[i] {
      return false
    }
  }
  return true
}

Why Heap Sort is Great

Heap sort is one of the most elegant sorting algorithms because it:

  1. Guarantees O(n log n): No worst-case scenarios like quicksort
  2. Uses O(1) space: Sorts in place with minimal memory overhead
  3. Is in-place: No need for temporary arrays
  4. Works well in practice: Despite being theoretically good, it has decent cache behavior

The main downside is that it’s not stable (doesn’t preserve the order of equal elements) and is slightly more complex to understand than simpler algorithms.

Conclusion

Awesome! We now have a working implementation of the heap sort algorithm in Go! This algorithm combines the best of both worlds: the guaranteed O(n log n) performance of merge sort with the O(1) space efficiency of insertion sort. It’s a powerful tool to have in your algorithm toolkit.

Quiz Time

Try your hand at these questions below to validate what we’ve just covered in this lesson: