Linked Lists in GoLang

What is a Linked List?

Linked list is a linear data structure whose elements are not stored in continuous memory blocks, unlike array.

This provides multiple benefits in comparison to an array including easy memory allocation, faster operations like insert and delete, etc

You can read more about it here

Creating a Linked List in GoLang

Pointers in GoLang

Pointers in Go are similar to pointers in languages like C/C++. A pointer holds memory address.

Consider the following example to understand pointers better

Code snippet for the above example will look like the following in Go

package main

import "fmt"

func main() {
	var p *int
	var i int

	i = 12

	p = &i
	fmt.Printf("Value of i is stored at memory address %p and is %d", p, *p)
}

Running the above code via go run main.go gives the following output

Value of i is stored at memory address 0x1400000e1b8 and is 12%                                                   

Importance of Pointers in Linked Lists

Unlike arrays, linked lists do not store data in continuous memory blocks. Therefore, if a linked list stores 4 integers, those 4 integers could be spread anywhere in the entire memory scope.

But then how to identify where the next value of the linked list is stored? This is where pointers come into picture. Each value of a linked list will store the value itself (int in this case) and a pointer (or the memory address) to the next value

Consider the following example, where a linked list with objects {2, 5, 12, 7} is created. All the blocks in red are memory locations already occupied

Now if we were to use an array, there is no place for 4 consecutive integers in the memory and therefore we would not be able to create an array. We’ll probably get an OOMKilled.

But linked list used the available 4 blocks of memory and spread the elements throughout them. Each element has a pointer (marked with green arrow) to the next element.

In practical, we will need more than 4 bytes for each element since we will have to store int and a pointer to the next int value.

But this is how a linked list looks in memory and one of the major difference between an array and a linked list.

The int value and pointer are packed together in a struct as follows

type Node struct { // a struct of type Node. This is the Node in a LL
  Val int // Val - the actual integer the node stores
  Next *Node // a pointer to the next node
}

LinkedList Implementation

A linked list will have a head. This will point to the first element of the linked list.

Subsequent elements can then be retrieved via the Next pointers

type LinkedList struct { // a struct named LinkedList
  Head *Node // pointer to the first node - the head of the LL
}

Operations on a Linked List

There can be multiple operations that can be done on a linked list, however the most common ones are

  1. Insertion
  2. Deletion
  3. Traversal

Code for these are provided below. There could be multiple different ways of implementing these

Insertion in a Linked List

Insertion in a linked list can be at any position, however we will be covering only insertion at the beginning of the linked list.

You can try the following on your own to test your understanding of linked list insertion

  1. Addition after an element
  2. Addition before an element
  3. Addition at the end of the linked list
  4. Addition to a particular position in linked list
type Node struct {
	Val int
	Next *Node
}

type LinkedList struct {
	Head *Node
}

func insertInLinkedList(ll *LinkedList, val int) {
	newNode := &Node{val, nil}
	if ll.Head == nil {
		ll.Head = newNode
	} else {
		newNode.Next = ll.Head
		ll.Head = newNode
	}
}

Deletion From A Linked List

Similar to insertion, deletion can be of many kinds in a linked list. We will however consider that we want to remove a node with a specific value from the linked list

type Node struct {
	Val int
	Next *Node
}

type LinkedList struct {
	Head *Node
}

func deleteFromLinkedList(ll *LinkedList, val int) bool {
	if ll.Head == nil {
		return false
	}

	if ll.Head.Val == val {
		ll.Head = ll.Head.Next
		return true
	}

	for prev, ptr := ll.Head, ll.Head.Next; ptr != nil; ptr = ptr.Next {
		if ptr.Val == val {
			prev.Next = ptr.Next
			return true
		}
		prev = ptr
	}

	return false
}

Traversing a Linked List

type Node struct {
	Val int
	Next *Node
}

type LinkedList struct {
	Head *Node
}

func traverseLinkedList(ll *LinkedList) {
	for ptr := ll.Head; ptr != nil; ptr = ptr.Next {
		fmt.Printf("| %d | - ", ptr.Val)
	}
	fmt.Printf("nil")
	fmt.Println()
}

Final Code

Following is the code for all of the above operations

package main

import "fmt"

type Node struct {
	Val int
	Next *Node
}

type LinkedList struct {
	Head *Node
}

func insertInLinkedList(ll *LinkedList, val int) {
	newNode := &Node{val, nil}
	if ll.Head == nil {
		ll.Head = newNode
	} else {
		newNode.Next = ll.Head
		ll.Head = newNode
	}
}

func deleteFromLinkedList(ll *LinkedList, val int) bool {
	if ll.Head == nil {
		return false
	}

	if ll.Head.Val == val {
		ll.Head = ll.Head.Next
		return true
	}

	for prev, ptr := ll.Head, ll.Head.Next; ptr != nil; ptr = ptr.Next {
		if ptr.Val == val {
			prev.Next = ptr.Next
			return true
		}
		prev = ptr
	}

	return false
}

func traverseLinkedList(ll *LinkedList) {
	for ptr := ll.Head; ptr != nil; ptr = ptr.Next {
		fmt.Printf("| %d | - ", ptr.Val)
	}
	fmt.Printf("nil")
	fmt.Println()
}

func main () {
	ll := &LinkedList{}
	insertInLinkedList(ll, 5)
	insertInLinkedList(ll, 3)
	insertInLinkedList(ll, 6)

	traverseLinkedList(ll)

	insertInLinkedList(ll, 12)
	traverseLinkedList(ll)

	deleteFromLinkedList(ll, 12)

	traverseLinkedList(ll)

}

Running the above code will yield the following output

❯ go run main.go
| 6 | - | 3 | - | 5 | - nil
| 12 | - | 6 | - | 3 | - | 5 | - nil
| 6 | - | 3 | - | 5 | - nil