Ver en Español
Data Structures in Go: Doubly Linked Lists
Jun 24, 2026
Updated: Jun 25, 2026

Data Structures in Go: Doubly Linked Lists

In this post we're going to explore doubly linked lists in Go, an incredibly useful and versatile data structure. You'll learn what doubly linked lists are, how to implement them in Go, and how to apply them in different practical cases. Let's get started.

What are doubly linked lists?

Doubly linked lists are a linear data structure similar to singly linked lists, but with one key difference: each node in a doubly linked list holds two pointers, one to the next node and one to the previous node. This lets you traverse the list in both directions, which makes certain operations easier to implement and improves efficiency in some cases.

A bit of context

Doubly linked lists have been an integral part of computing since its earliest days. They are a fundamental structure that has been used since the early days of computing and, ever since, they have been applied across a wide variety of algorithms and applications, from operating systems to web browsers.

Implementing doubly linked lists in Go

Let's look at a basic example of how to implement a doubly linked list in Go.

package main

import "fmt"

type Node struct {
	data int
	prev *Node
	next *Node
}

type DoublyLinkedList struct {
	head *Node
	tail *Node
}

func (list *DoublyLinkedList) Append(value int) {
	newNode := &Node{data: value, prev: nil, next: nil}

	if list.head == nil {
		list.head = newNode
		list.tail = newNode
	} else {
		newNode.prev = list.tail
		list.tail.next = newNode
		list.tail = newNode
	}
}

func (list *DoublyLinkedList) Print() {
	current := list.head
	for current != nil {
		fmt.Printf("%d ", current.data)
		current = current.next
	}
	fmt.Println()
}

func main() {
	list := DoublyLinkedList{}

	list.Append(1)
	list.Append(2)
	list.Append(3)
	list.Append(4)

	fmt.Println("Lista doblemente enlazada:")
	list.Print()
}

This basic implementation includes a Node struct with prev and next pointers, as well as a DoublyLinkedList struct with head and tail pointers. The Append() method lets you add elements to the end of the list and the Print() method prints the list's elements.

Use cases for doubly linked lists

Doubly linked lists are ideal for a wide variety of applications. Here are some situations where you might use them:

  1. Web browsers, to implement browsing history, where users can move forward and back between visited pages.
  2. Text or image editing systems that support undo and redo, storing actions in a structure that is easy to navigate in both directions.
  3. Implementing advanced algorithms, such as Dijkstra's algorithm to find the shortest path in a weighted graph.
  4. Memory management in operating systems, where doubly linked lists make it easier to allocate and free resources efficiently.
  5. Storing and managing playlists in multimedia applications, where users can easily navigate forward and back through songs or videos.

Methods of doubly linked lists

Doubly linked lists in Go, as in other programming languages, have several common methods used to manipulate and work with the data structure. Below are some of the most common methods and an explanation of each.

Append: this method is used to add an element to the end of the doubly linked list. It creates a new node with the provided value, sets its prev and next pointers correctly, and updates the list's tail.

func (list *DoublyLinkedList) Append(value int) {
	newNode := &Node{data: value, prev: nil, next: nil}

	if list.head == nil {
		list.head = newNode
		list.tail = newNode
	} else {
		newNode.prev = list.tail
		list.tail.next = newNode
		list.tail = newNode
	}
}

InsertAt: this method inserts an element at a specific position in the doubly linked list. It creates a new node with the provided value and adjusts the prev and next pointers of the adjacent nodes. Notice that we guard the empty list case, so inserting at position 0 of an empty list does not trigger a panic and also updates the tail.

func (list *DoublyLinkedList) InsertAt(value int, position int) error {
	if position < 0 {
		return fmt.Errorf("posición inválida")
	}

	newNode := &Node{data: value, prev: nil, next: nil}
	current := list.head
	index := 0

	if position == 0 {
		newNode.next = list.head
		if list.head != nil {
			list.head.prev = newNode
		} else {
			list.tail = newNode
		}
		list.head = newNode
	} else {
		for current != nil && index < position-1 {
			current = current.next
			index++
		}

		if current == nil {
			return fmt.Errorf("posición fuera de rango")
		}

		newNode.prev = current
		newNode.next = current.next
		current.next = newNode

		if newNode.next != nil {
			newNode.next.prev = newNode
		} else {
			list.tail = newNode
		}
	}

	return nil
}

RemoveAt: this method removes an element from the doubly linked list at a specific position. It finds the node at the given position and adjusts the prev and next pointers of the adjacent nodes before removing the node.

func (list *DoublyLinkedList) RemoveAt(position int) error {
	if position < 0 || list.head == nil {
		return fmt.Errorf("posición inválida o lista vacía")
	}

	current := list.head
	index := 0

	if position == 0 {
		list.head = current.next
		if list.head != nil {
			list.head.prev = nil
		} else {
			list.tail = nil
		}
	} else {
		for current != nil && index < position {
			current = current.next
			index++
		}

		if current == nil {
			return fmt.Errorf("posición fuera de rango")
		}

		current.prev.next = current.next
		if current.next != nil {
			current.next.prev = current.prev
		} else {
			list.tail = current.prev
		}
	}

	return nil
}

Size: this method returns the number of elements in the doubly linked list. It traverses the list and counts the nodes until it reaches the end.

func (list *DoublyLinkedList) Size() int {
	count := 0
	current := list.head

	for current != nil {
		count++
		current = current.next
	}

	return count
}

Get: this method returns a pointer to the node at the specified index of the doubly linked list. It traverses the list until it finds the node at the requested position.

func (list *DoublyLinkedList) Get(index int) (*Node, error) {
	if index < 0 {
		return nil, fmt.Errorf("índice inválido")
	}

	current := list.head
	count := 0

	for current != nil && count < index {
		current = current.next
		count++
	}

	if current == nil {
		return nil, fmt.Errorf("índice fuera de rango")
	}

	return current, nil
}

Reverse: this method reverses the order of the elements in the doubly linked list. It swaps the prev and next pointers of each node and, once done, swaps the list's head and tail. That last line is the key: since we already flipped all the internal pointers, we just need to swap head and tail so the list ends up correctly reversed.

func (list *DoublyLinkedList) Reverse() {
	current := list.head

	for current != nil {
		current.prev, current.next = current.next, current.prev
		current = current.prev
	}

	list.head, list.tail = list.tail, list.head
}

These are some of the most common methods used when working with doubly linked lists in Go. Implementing and understanding these methods will let you manipulate and manage doubly linked lists efficiently in your programming projects.

Exercises to practice doubly linked lists in Go

Here are some exercises to practice and improve your skills with doubly linked lists in Go. When you're done, send your solution as a Pull request to this repository: Go Para Principiantes.

  1. Implement an InsertAt(value int, position int) method to insert an element at a specific position in the list.
  2. Implement a RemoveAt(position int) method to remove an element from the list given its index.
  3. Create a Size() method that returns the number of elements in the doubly linked list.
  4. Implement a Get(index int) (*Node, error) method that returns a pointer to the node at the specified index.
  5. Implement a Reverse() method to reverse the order of the elements in the doubly linked list.
  6. Create a RemoveDuplicates() method that removes duplicate elements from a doubly linked list.
  7. Implement a FindMiddle() *Node method that finds and returns a pointer to the node of the list's middle element.
  8. Create a HasCycle() bool method to detect cycles in a doubly linked list.
  9. Implement a SwapNodes(node1, node2 *Node) method that swaps two nodes in a doubly linked list.
  10. Create a Sort() method to sort the elements of the doubly linked list in ascending or descending order.

Don't forget to test your implementations and experiment with different use cases to better understand doubly linked lists in Go.

Note: verify that the exercises repository URL still resolves before publishing, since the original text only showed the link's name and not the address. verify

Conclusion

Doubly linked lists are a powerful, versatile tool in the world of programming. By mastering this data structure in Go, you'll be able to tackle a wide range of problems and applications efficiently and elegantly.

Remember that, although doubly linked lists are amazing, it's essential to choose the right data structure for each situation. Keep learning and exploring, and don't hesitate to dive into other topics like trees, graphs, and algorithms. This post is part of a series on Go, so I invite you to check out the other articles to keep going deeper.

That's all, I hope this post is useful to you and that you can apply it to a project you have in mind. Leave me a comment if it helped, if you want to add an opinion, or if you have any questions. And remember, if you liked it, you can also share it using the social links below. Good luck on your learning journey.

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias