25% off

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

Get started →

Video:

Implementing Merge 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 merge 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 03-merge-sort/. We’ll build up the merge sort implementation step by step.

Let’s start with the basic setup:

package main

import "fmt"

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

Now, let’s define the function signatures for our merge sort functions. We need two functions: one for the main merge sort logic and one for merging two sorted slices:

// MergeSort - takes in a slice of ints and returns a sorted slice of ints
func MergeSort(input []int) []int {

}

// Merge - takes two sorted slices and merges them into a single sorted slice
func Merge(left, right []int) []int {

}

Let’s start with the Merge function first since it’s simpler. This function takes two already-sorted slices and combines them into a single sorted slice:

func Merge(left, right []int) []int {
  result := make([]int, 0, len(left)+len(right))

  // iterate through both slices and add the smaller element to result
  for len(left) > 0 || len(right) > 0 {
    // if left is empty, add all remaining elements from right
    if len(left) == 0 {
      return append(result, right...)
    }
    // if right is empty, add all remaining elements from left
    if len(right) == 0 {
      return append(result, left...)
    }

    // compare first elements and add the smaller one
    if left[0] <= right[0] {
      result = append(result, left[0])
      left = left[1:]
    } else {
      result = append(result, right[0])
      right = right[1:]
    }
  }

  return result
}

The Merge function works by comparing the first elements of each slice and adding the smaller one to the result. We continue until both slices are empty.

Now let’s implement the MergeSort function, which uses the divide-and-conquer approach:

func MergeSort(input []int) []int {
  // base case: arrays with 0 or 1 element are already sorted
  if len(input) <= 1 {
    return input
  }

  // divide the array into two halves
  middle := len(input) / 2
  left := MergeSort(input[:middle])
  right := MergeSort(input[middle:])

  // merge the sorted halves
  return Merge(left, right)
}

This is elegant! The function:

  1. Checks if the input has 1 or fewer elements (base case)
  2. Divides the input in half
  3. Recursively sorts each half
  4. Merges the sorted halves back together

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

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

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

  sorted := MergeSort(unsortedInput)

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

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

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

Excellent! Our merge sort is working correctly. Let’s add a version with some debug output to see what’s happening at each step:

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

  fmt.Printf("%sDividing: %v\n", indent, input)

  if len(input) <= 1 {
    return input
  }

  middle := len(input) / 2
  left := MergeSortDebug(input[:middle], depth+1)
  right := MergeSortDebug(input[middle:], depth+1)

  result := Merge(left, right)
  fmt.Printf("%sMerged: %v\n", indent, result)

  return result
}

Now when we call it with debug output, we can see exactly how the array is being divided and merged:

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

Perfect! You can see how the algorithm divides the array all the way down to single elements, then merges them back together in sorted order.

Testing

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

func TestMergeSort(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}},
  }

  for _, tt := range tests {
    sorted := MergeSort(tt.input)
    if !slicesEqual(sorted, tt.expected) {
      t.Errorf("MergeSort(%v) = %v, want %v", tt.input, sorted, 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
}

Conclusion

Awesome! We now have a working implementation of the merge sort algorithm in Go! This algorithm is much more efficient than bubble sort or insertion sort for larger datasets because it guarantees O(n log n) performance. The divide-and-conquer approach makes it easy to understand and implement correctly.

Quiz Time

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