25% off

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

Get started →

Video:

Implementing Tree Search 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 be implementing a Binary Search Tree in Go.

Implementation

Let’s start off by creating a new file called main.go within a new directory called 09-bst/. We’ll build a complete BST implementation from scratch.

First, let’s define our Node struct:

package main

import "fmt"

// Node represents a single node in the Binary Search Tree
type Node struct {
	value int
	left  *Node
	right *Node
}

// NewNode creates a new node with a given value
func NewNode(value int) *Node {
	return &Node{value: value}
}

Now let’s define our BST struct with the essential methods:

// BST represents a Binary Search Tree
type BST struct {
	root *Node
}

// NewBST creates a new empty Binary Search Tree
func NewBST() *BST {
	return &BST{}
}

// Insert adds a new value to the BST
func (b *BST) Insert(value int) {
	if b.root == nil {
		b.root = NewNode(value)
	} else {
		b.insertNode(b.root, value)
	}
}

// insertNode is a helper function that recursively inserts a value
func (b *BST) insertNode(node *Node, value int) {
	if value < node.value {
		// Go left
		if node.left == nil {
			node.left = NewNode(value)
		} else {
			b.insertNode(node.left, value)
		}
	} else {
		// Go right
		if node.right == nil {
			node.right = NewNode(value)
		} else {
			b.insertNode(node.right, value)
		}
	}
}

// Search looks for a value in the BST
// Returns true if found, false otherwise
func (b *BST) Search(value int) bool {
	return b.searchNode(b.root, value)
}

// searchNode is a helper function that recursively searches for a value
func (b *BST) searchNode(node *Node, value int) bool {
	// Base case: node is nil
	if node == nil {
		return false
	}

	fmt.Printf("Checking node with value %d\n", node.value)

	if value == node.value {
		return true
	} else if value < node.value {
		// Search left
		return b.searchNode(node.left, value)
	} else {
		// Search right
		return b.searchNode(node.right, value)
	}
}

Now let’s create a main function to test our BST:

func main() {
	fmt.Println("Binary Search Tree Implementation in Go")

	// Create a new BST
	bst := NewBST()

	// Insert values
	values := []int{50, 30, 70, 20, 40, 60, 80}
	for _, val := range values {
		bst.Insert(val)
		fmt.Printf("Inserted %d\n", val)
	}

	fmt.Println("\nSearching for 35:")
	found := bst.Search(35)
	fmt.Printf("Result: %v\n", found)

	fmt.Println("\nSearching for 40:")
	found = bst.Search(40)
	fmt.Printf("Result: %v\n", found)
}

Running this will output:

$ go run main.go
Binary Search Tree Implementation in Go
Inserted 50
Inserted 30
Inserted 70
Inserted 20
Inserted 40
Inserted 60
Inserted 80

Searching for 35:
Checking node with value 50
Checking node with value 30
Checking node with value 40
Result: false

Searching for 40:
Checking node with value 50
Checking node with value 30
Checking node with value 40
Result: true

Tree Traversal Methods

Let’s add different ways to traverse the tree:

// InOrder traversal: Left, Node, Right (gives sorted order)
func (b *BST) InOrder() []int {
	var result []int
	b.inOrderHelper(b.root, &result)
	return result
}

func (b *BST) inOrderHelper(node *Node, result *[]int) {
	if node == nil {
		return
	}
	b.inOrderHelper(node.left, result)
	*result = append(*result, node.value)
	b.inOrderHelper(node.right, result)
}

// PreOrder traversal: Node, Left, Right
func (b *BST) PreOrder() []int {
	var result []int
	b.preOrderHelper(b.root, &result)
	return result
}

func (b *BST) preOrderHelper(node *Node, result *[]int) {
	if node == nil {
		return
	}
	*result = append(*result, node.value)
	b.preOrderHelper(node.left, result)
	b.preOrderHelper(node.right, result)
}

// PostOrder traversal: Left, Right, Node
func (b *BST) PostOrder() []int {
	var result []int
	b.postOrderHelper(b.root, &result)
	return result
}

func (b *BST) postOrderHelper(node *Node, result *[]int) {
	if node == nil {
		return
	}
	b.postOrderHelper(node.left, result)
	b.postOrderHelper(node.right, result)
	*result = append(*result, node.value)
}

Test the traversals:

func main() {
	bst := NewBST()
	values := []int{50, 30, 70, 20, 40, 60, 80}
	for _, val := range values {
		bst.Insert(val)
	}

	fmt.Println("In-Order (sorted):", bst.InOrder())
	fmt.Println("Pre-Order:", bst.PreOrder())
	fmt.Println("Post-Order:", bst.PostOrder())
}

Output:

In-Order (sorted): [20 30 40 50 60 70 80]
Pre-Order: [50 30 20 40 70 60 80]
Post-Order: [20 40 30 60 80 70 50]

Finding Minimum and Maximum

Let’s add utility methods:

// FindMin returns the minimum value in the BST
func (b *BST) FindMin() int {
	if b.root == nil {
		return -1 // or handle error
	}
	return b.findMinNode(b.root).value
}

func (b *BST) findMinNode(node *Node) *Node {
	for node.left != nil {
		node = node.left
	}
	return node
}

// FindMax returns the maximum value in the BST
func (b *BST) FindMax() int {
	if b.root == nil {
		return -1 // or handle error
	}
	return b.findMaxNode(b.root).value
}

func (b *BST) findMaxNode(node *Node) *Node {
	for node.right != nil {
		node = node.right
	}
	return node
}

Testing Our Implementation

Here are some basic tests:

func TestBSTSearch(t *testing.T) {
	bst := NewBST()
	values := []int{50, 30, 70, 20, 40, 60, 80}
	for _, val := range values {
		bst.Insert(val)
	}

	// Test finding existing values
	for _, val := range values {
		if !bst.Search(val) {
			t.Errorf("Failed to find %d", val)
		}
	}

	// Test finding non-existing values
	if bst.Search(100) {
		t.Error("Found non-existing value")
	}
	if bst.Search(25) {
		t.Error("Found non-existing value")
	}
}

func TestBSTInOrder(t *testing.T) {
	bst := NewBST()
	values := []int{50, 30, 70, 20, 40, 60, 80}
	for _, val := range values {
		bst.Insert(val)
	}

	inOrder := bst.InOrder()
	expected := []int{20, 30, 40, 50, 60, 70, 80}

	if len(inOrder) != len(expected) {
		t.Errorf("Expected %d elements, got %d", len(expected), len(inOrder))
	}

	for i, val := range inOrder {
		if val != expected[i] {
			t.Errorf("At index %d: expected %d, got %d", i, expected[i], val)
		}
	}
}

func TestBSTMinMax(t *testing.T) {
	bst := NewBST()
	values := []int{50, 30, 70, 20, 40, 60, 80}
	for _, val := range values {
		bst.Insert(val)
	}

	if bst.FindMin() != 20 {
		t.Error("FindMin failed")
	}
	if bst.FindMax() != 80 {
		t.Error("FindMax failed")
	}
}

Conclusion

Awesome! We now have a working implementation of a Binary Search Tree in Go. We’ve implemented insertion, search, multiple traversal methods, and utility functions for finding minimum and maximum values. BSTs are fundamental data structures that provide efficient searching when balanced, and they naturally maintain sorted order.

Quiz Time

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