diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..dbf20f2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 HotPotatoC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..18577b8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# Sture
+
+A collection of data structures based on Go 1.18+ Generics.
+
+## Installation
+
+```bash
+go get github.com/HotPotatoC/sture
+```
+
+## Usage
+
+Creating a priority queue using:
+
+```go
+import "github.com/HotPotatoC/sture/queue"
+
+func main() {
+    pq := queue.NewPriorityQueue[string]()
+
+    pq.Enqueue("Adam", 1)
+    pq.Enqueue("John", 3)
+    pq.Enqueue("Bob", 2)
+
+    top := pq.Peek()
+    fmt.Println(top) // John
+}
+```
+
+## Support
+
+If this project is helpful to you, please consider supporting me by donating or just give this project a 🌟
+
+<a href="https://www.buymeacoffee.com/hotpotato" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
diff --git a/example/main.go b/example/main.go
new file mode 100644
index 0000000..878a6c7
--- /dev/null
+++ b/example/main.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/HotPotatoC/sture/queue"
+)
+
+func main() {
+    pq := queue.NewPriorityQueue[string]()
+
+    pq.Enqueue("Adam", 1)
+    pq.Enqueue("John", 3)
+    pq.Enqueue("Bob", 2)
+
+    top := pq.Peek()
+    fmt.Println(top) // John
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..8e65bae
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/HotPotatoC/sture
+
+go 1.18
+
+require golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..0507dff
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 h1:s/+U+w0teGzcoH2mdIlFQ6KfVKGaYpgyGdUefZrn9TU=
+golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
diff --git a/linkedlist/circular_linked_list.go b/linkedlist/circular_linked_list.go
new file mode 100644
index 0000000..dd157bd
--- /dev/null
+++ b/linkedlist/circular_linked_list.go
@@ -0,0 +1,221 @@
+package linkedlist
+
+import (
+	"errors"
+	"fmt"
+
+	"golang.org/x/exp/constraints"
+)
+
+// CircularLinkedList is a circular linked list.
+type CircularLinkedList[V constraints.Ordered] struct {
+	head *Node[V]
+	tail *Node[V]
+	nSize uint
+}
+
+// NewCircularLinkedList returns a new circular linked list.
+func NewCircularLinkedList[V constraints.Ordered]() *CircularLinkedList[V] {
+	return &CircularLinkedList[V]{}
+}
+
+// Append adds a new node to the end of the list.
+func (ll *CircularLinkedList[V]) Append(value V) {
+	newNode := NewNode(value)
+	ll.nSize++
+
+	if ll.head == nil {
+		ll.head = newNode
+		ll.tail = newNode
+		return
+	}
+
+	newNode.prev = ll.tail
+	newNode.next = ll.head
+	ll.tail.next = newNode
+	ll.tail = ll.tail.next
+	ll.head.prev = ll.tail
+}
+
+// PushHead adds a new node to the head of the list.
+func (ll *CircularLinkedList[V]) PushHead(value V) {
+	newNode := NewNode(value)
+	ll.nSize++
+
+	if ll.head == nil {
+		ll.head = newNode
+		ll.tail = newNode
+		return
+	}
+
+	newNode.next = ll.head
+	ll.head.prev = newNode
+	ll.head = newNode
+	ll.tail.next = ll.head
+}
+
+// InsertAt adds a new node to the given position
+func (ll *CircularLinkedList[V]) InsertAt(pos int, value V) error {
+	newNode := NewNode(value)
+
+	if pos < 1 || uint(pos) > ll.nSize {
+		return errors.New("invalid position")
+	}
+
+	currPos := 1
+	currNode := ll.head
+
+	if pos == 1 {
+		ll.PushHead(value)
+		return nil
+	}
+
+	for currPos < pos {
+		currNode = currNode.next
+		currPos++
+	}
+
+	newNode.prev = currNode.prev
+	newNode.next = currNode
+	currNode.prev.next = newNode
+	currNode.prev = newNode
+
+	ll.nSize++
+
+	return nil
+}
+
+// PushMid adds a new node to the middle of the list.
+func (ll *CircularLinkedList[V]) PushMid(value V) {
+	newNode := NewNode(value)
+	ll.nSize++
+
+	if ll.head == nil {
+		ll.head = newNode
+		ll.tail = newNode
+		return
+	}
+
+	if ll.head.value > value {
+		ll.PushHead(value)
+		return
+	}
+
+	if ll.tail.value < value {
+		ll.Append(value)
+		return
+	}
+
+	curr := ll.head
+
+	for curr.value < value {
+		curr = curr.next
+	}
+
+	newNode.prev = curr.prev
+	newNode.next = curr
+	curr.prev.next = newNode
+	curr.prev = newNode
+}
+
+// Pop removes the last node from the list.
+func (ll *CircularLinkedList[V]) Pop() {
+	if ll.head == nil {
+		return
+	}
+
+	ll.nSize--
+
+	if ll.head == ll.tail {
+		ll.head = nil
+		ll.tail = nil
+		return
+	}
+
+	prev := ll.tail.prev
+	prev.next = ll.head
+	ll.tail = prev
+	ll.head.prev = ll.tail
+}
+
+// PopHead removes the first node from the list.
+func (ll *CircularLinkedList[V]) PopHead() {
+	if ll.head == nil {
+		return
+	}
+
+	ll.nSize--
+
+	if ll.head == ll.tail {
+		ll.head = nil
+		ll.tail = nil
+		return
+	}
+
+	next := ll.head.next
+	next.prev = ll.tail
+	ll.head = next
+	ll.tail.next = ll.head
+}
+
+// Find returns the node with the given value.
+func (ll *CircularLinkedList[V]) Find(value V) *Node[V] {
+	curr := ll.head
+
+	for curr != nil {
+		if curr.value == value {
+			return curr
+		}
+
+		curr = curr.next
+	}
+
+	return nil
+}
+
+// String returns a string representation of the list.
+func (ll *CircularLinkedList[V]) String() string {
+	var s string
+
+	curr := ll.head
+
+	for curr != nil {
+		s += fmt.Sprintf("[%v]", curr.value)
+		if curr.next != ll.head {
+			s += `-`
+		}
+
+		if curr.next == ll.head {
+			return s
+		}
+
+		curr = curr.next
+	}
+
+	return s
+}
+
+// Head returns the head node of the list.
+func (ll *CircularLinkedList[V]) Head() *Node[V] {
+	return ll.head
+}
+
+// Tail returns the tail node of the list.
+func (ll *CircularLinkedList[V]) Tail() *Node[V] {
+	return ll.tail
+}
+
+// Size returns the size of the list.
+func (ll *CircularLinkedList[V]) Size() uint {
+	return ll.nSize
+}
+
+// IsCircular returns true if the list is circular.
+func (ll *CircularLinkedList[V]) IsCircular() bool {
+	return ll.head != nil && ll.head == ll.tail.next
+}
+
+// IsEmpty returns true if the list is empty.
+func (ll *CircularLinkedList[V]) IsEmpty() bool {
+	return ll.head == nil
+}
diff --git a/linkedlist/circular_linked_list_test.go b/linkedlist/circular_linked_list_test.go
new file mode 100644
index 0000000..b143b17
--- /dev/null
+++ b/linkedlist/circular_linked_list_test.go
@@ -0,0 +1,139 @@
+package linkedlist_test
+
+import (
+	"testing"
+
+	"github.com/HotPotatoC/sture/linkedlist"
+)
+
+func TestCircularLinkedList_Append(t *testing.T) {
+	ll := linkedlist.NewCircularLinkedList[int]()
+	exp := "[1]-[2]-[3]-[4]-[5]"
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+	ll.Append(4)
+	ll.Append(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestCircularLinkedList_PushHead(t *testing.T) {
+	ll := linkedlist.NewCircularLinkedList[int]()
+	exp := "[5]-[4]-[3]-[2]-[1]"
+
+	ll.PushHead(1)
+	ll.PushHead(2)
+	ll.PushHead(3)
+	ll.PushHead(4)
+	ll.PushHead(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestCircularLinkedList_Pop(t *testing.T) {
+	ll := linkedlist.NewCircularLinkedList[int]()
+	exp := "[1]-[2]-[5]"
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+	ll.Pop()
+	ll.Append(4)
+	ll.Pop()
+	ll.Append(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = "[1]"
+
+	ll.Pop()
+	ll.Pop()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = ""
+
+	ll.Pop()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestCircularLinkedList_PopHead(t *testing.T) {
+	ll := linkedlist.NewCircularLinkedList[int]()
+	exp := "[5]-[2]-[1]"
+
+	ll.PushHead(1)
+	ll.PushHead(2)
+	ll.PushHead(3)
+	ll.PopHead()
+	ll.PushHead(4)
+	ll.PopHead()
+	ll.PushHead(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = "[1]"
+
+	ll.PopHead()
+	ll.PopHead()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = ""
+
+	ll.PopHead()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestCircularLinkedList_Find(t *testing.T) {
+	ll := linkedlist.NewCircularLinkedList[int]()
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+
+	node := ll.Find(2)
+
+	if node.Next().Value() != 3 || node.Prev().Value() != 1 {
+		t.Errorf("\nExpected: next=3 prev=1\nGot: next=%d prev=%d", node.Next().Value(), node.Prev().Value())
+	}
+}
+
+func TestCircularLinkedList_IsCircular(t *testing.T) {
+	ll := linkedlist.NewCircularLinkedList[int]()
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+
+	if ll.Head().Prev() != ll.Tail() {
+		t.Errorf("\nExpected: prev=tail\nGot: prev=%d", ll.Head().Prev().Value())
+	}
+
+	if ll.Tail().Next() != ll.Head() {
+		t.Errorf("\nExpected: next=head\nGot: next=%d", ll.Tail().Next().Value())
+	}
+
+	if !ll.IsCircular() {
+		t.Errorf("\nExpected: circular")
+	}
+}
diff --git a/linkedlist/linked_list.go b/linkedlist/linked_list.go
new file mode 100644
index 0000000..41a6d20
--- /dev/null
+++ b/linkedlist/linked_list.go
@@ -0,0 +1,207 @@
+package linkedlist
+
+import (
+	"errors"
+	"fmt"
+
+	"golang.org/x/exp/constraints"
+)
+
+// LinkedList is a linked list.
+type LinkedList[V constraints.Ordered] struct {
+	head  *Node[V]
+	tail  *Node[V]
+	nSize uint
+}
+
+// NewLinkedList returns a new linked list.
+func NewLinkedList[V constraints.Ordered]() *LinkedList[V] {
+	return &LinkedList[V]{}
+}
+
+// Append adds a new node to the end of the list.
+func (ll *LinkedList[V]) Append(value V) {
+	newNode := NewNode(value)
+	ll.nSize++
+
+	if ll.head == nil {
+		ll.head = newNode
+		ll.tail = newNode
+		return
+	}
+
+	newNode.prev = ll.tail
+	ll.tail.next = newNode
+	ll.tail = ll.tail.next
+}
+
+// PushHead adds a new node to the head of the list.
+func (ll *LinkedList[V]) PushHead(value V) {
+	newNode := NewNode(value)
+	ll.nSize++
+
+	if ll.head == nil {
+		ll.head = newNode
+		ll.tail = newNode
+		return
+	}
+
+	newNode.next = ll.head
+	ll.head.prev = newNode
+	ll.head = newNode
+}
+
+// InsertAt adds a new node to the given position
+func (ll *LinkedList[V]) InsertAt(pos int, value V) error {
+	newNode := NewNode(value)
+
+	if pos < 1 || uint(pos) > ll.nSize {
+		return errors.New("invalid position")
+	}
+
+	currPos := 1
+	currNode := ll.head
+
+	if pos == 1 {
+		ll.PushHead(value)
+		return nil
+	}
+
+	for currPos < pos {
+		currNode = currNode.next
+		currPos++
+	}
+
+	newNode.prev = currNode.prev
+	newNode.next = currNode
+	currNode.prev.next = newNode
+	currNode.prev = newNode
+
+	ll.nSize++
+
+	return nil
+}
+
+// PushMid adds a new node to the middle of the list.
+func (ll *LinkedList[V]) PushMid(value V) {
+	newNode := NewNode(value)
+	ll.nSize++
+
+	if ll.head == nil {
+		ll.head = newNode
+		ll.tail = newNode
+		return
+	}
+
+	if ll.head.value > value {
+		ll.PushHead(value)
+		return
+	}
+
+	if ll.tail.value < value {
+		ll.Append(value)
+		return
+	}
+
+	curr := ll.head
+
+	for curr.value < value {
+		curr = curr.next
+	}
+
+	newNode.prev = curr.prev
+	newNode.next = curr
+	curr.prev.next = newNode
+	curr.prev = newNode
+}
+
+// Pop removes the last node from the list.
+func (ll *LinkedList[V]) Pop() {
+	if ll.head == nil {
+		return
+	}
+
+	ll.nSize--
+
+	if ll.head == ll.tail {
+		ll.head = nil
+		ll.tail = nil
+		return
+	}
+
+	prev := ll.tail.prev
+	prev.next = nil
+	ll.tail = prev
+}
+
+// PopHead removes the first node from the list.
+func (ll *LinkedList[V]) PopHead() {
+	if ll.head == nil {
+		return
+	}
+
+	ll.nSize--
+
+	if ll.head == ll.tail {
+		ll.head = nil
+		ll.tail = nil
+		return
+	}
+
+	next := ll.head.next
+	next.prev = nil
+	ll.head = next
+}
+
+// Find returns the node with the given value.
+func (ll *LinkedList[V]) Find(value V) *Node[V] {
+	curr := ll.head
+
+	for curr != nil {
+		if curr.value == value {
+			return curr
+		}
+
+		curr = curr.next
+	}
+
+	return nil
+}
+
+// String returns a string representation of the list.
+func (ll *LinkedList[V]) String() string {
+	var s string
+
+	curr := ll.head
+
+	for curr != nil {
+		s += fmt.Sprintf("[%v]", curr.value)
+		if curr.next != nil {
+			s += `-`
+		}
+
+		curr = curr.next
+	}
+
+	return s
+}
+
+// Head returns the head node of the list.
+func (ll *LinkedList[V]) Head() *Node[V] {
+	return ll.head
+}
+
+// Tail returns the tail node of the list.
+func (ll *LinkedList[V]) Tail() *Node[V] {
+	return ll.tail
+}
+
+// Size returns the size of the list.
+func (ll *LinkedList[V]) Size() uint {
+	return ll.nSize
+}
+
+// IsEmpty returns true if the list is empty.
+func (ll *LinkedList[V]) IsEmpty() bool {
+	return ll.head == nil
+}
\ No newline at end of file
diff --git a/linkedlist/linked_list_test.go b/linkedlist/linked_list_test.go
new file mode 100644
index 0000000..dcac8bd
--- /dev/null
+++ b/linkedlist/linked_list_test.go
@@ -0,0 +1,189 @@
+package linkedlist_test
+
+import (
+	"testing"
+
+	"github.com/HotPotatoC/sture/linkedlist"
+)
+
+func TestLinkedList_Append(t *testing.T) {
+	ll := linkedlist.NewLinkedList[int]()
+	exp := "[1]-[2]-[3]-[4]-[5]"
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+	ll.Append(4)
+	ll.Append(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestLinkedList_PushHead(t *testing.T) {
+	ll := linkedlist.NewLinkedList[int]()
+	exp := "[5]-[4]-[3]-[2]-[1]"
+
+	ll.PushHead(1)
+	ll.PushHead(2)
+	ll.PushHead(3)
+	ll.PushHead(4)
+	ll.PushHead(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestLinkedList_InsertAt(t *testing.T) {
+	ll := linkedlist.NewLinkedList[int]()
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+
+	ll.InsertAt(2, 4)
+
+	exp := "[1]-[4]-[2]-[3]"
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	ll.InsertAt(1, 5)
+
+	exp = "[5]-[1]-[4]-[2]-[3]"
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	ll.InsertAt(5, 6)
+
+	exp = "[5]-[1]-[4]-[2]-[6]-[3]"
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	err := ll.InsertAt(7, 7)
+	if err == nil {
+		t.Errorf("\nExpected: error\nGot: nil")
+	}
+}
+
+func TestLinkedList_PushMid(t *testing.T) {
+	ll := linkedlist.NewLinkedList[int]()
+	exp := "[1]-[2]-[3]-[4]-[5]"
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+	ll.Append(4)
+	ll.PushMid(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	ll.PushMid(-4)
+	ll.PushMid(-2)
+
+	exp = "[-4]-[-2]-[1]-[2]-[3]-[4]-[5]"
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	ll.PushMid(3)
+
+	exp = "[-4]-[-2]-[1]-[2]-[3]-[3]-[4]-[5]"
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestLinkedList_Pop(t *testing.T) {
+	ll := linkedlist.NewLinkedList[int]()
+	exp := "[1]-[2]-[5]"
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+	ll.Pop()
+	ll.Append(4)
+	ll.Pop()
+	ll.Append(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = "[1]"
+
+	ll.Pop()
+	ll.Pop()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = ""
+
+	ll.Pop()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestLinkedList_PopHead(t *testing.T) {
+	ll := linkedlist.NewLinkedList[int]()
+	exp := "[5]-[2]-[1]"
+
+	ll.PushHead(1)
+	ll.PushHead(2)
+	ll.PushHead(3)
+	ll.PopHead()
+	ll.PushHead(4)
+	ll.PopHead()
+	ll.PushHead(5)
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = "[1]"
+
+	ll.PopHead()
+	ll.PopHead()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+
+	exp = ""
+
+	ll.PopHead()
+
+	if ll.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, ll.String())
+	}
+}
+
+func TestLinkedList_Find(t *testing.T) {
+	ll := linkedlist.NewLinkedList[int]()
+
+	ll.Append(1)
+	ll.Append(2)
+	ll.Append(3)
+
+	node := ll.Find(2)
+
+	if node.Next().Value() != 3 || node.Prev().Value() != 1 {
+		t.Errorf("\nExpected: next=3 prev=1\nGot: next=%d prev=%d",
+			node.Next().Value(), node.Prev().Value())
+	}
+}
diff --git a/linkedlist/node.go b/linkedlist/node.go
new file mode 100644
index 0000000..30c065e
--- /dev/null
+++ b/linkedlist/node.go
@@ -0,0 +1,30 @@
+package linkedlist
+
+import "golang.org/x/exp/constraints"
+
+// Node is a node in a linked list.
+type Node[V constraints.Ordered] struct {
+	value V
+	next  *Node[V]
+	prev  *Node[V]
+}
+
+// NewNode returns a new linked list node.
+func NewNode[V constraints.Ordered](value V) *Node[V] {
+	return &Node[V]{value: value}
+}
+
+// Value returns the value of the node.
+func (node *Node[V]) Value() V {
+	return node.value
+}
+
+// Next returns the next node in the list.
+func (node *Node[V]) Next() *Node[V] {
+	return node.next
+}
+
+// Prev returns the previous node in the list.
+func (node *Node[V]) Prev() *Node[V] {
+	return node.prev
+}
diff --git a/queue/priority_queue.go b/queue/priority_queue.go
new file mode 100644
index 0000000..c71d8bb
--- /dev/null
+++ b/queue/priority_queue.go
@@ -0,0 +1,84 @@
+package queue
+
+import (
+	"fmt"
+
+	"golang.org/x/exp/constraints"
+)
+
+// PriorityQueue is a queue that supports priority-based insertion.
+type PriorityQueue[V constraints.Ordered] struct {
+	list  []PriorityQueueNode[V]
+	nSize int
+}
+
+// NewPriorityQueue creates a new priority queue.
+func NewPriorityQueue[V constraints.Ordered]() *PriorityQueue[V] {
+	return &PriorityQueue[V]{
+		list: make([]PriorityQueueNode[V], 0),
+	}
+}
+
+// Enqueue adds a new node at a certain position in the queue based on the priority.
+func (q *PriorityQueue[V]) Enqueue(value V, priority int) {
+	newNode := NewPriorityQueueNode(value, priority)
+
+	pos := q.search(priority) // search for the position to insert the new node
+
+	q.list = append(q.list, PriorityQueueNode[V]{}) // add empty node
+
+	copy(q.list[pos+1:], q.list[pos:]) // shift elements to the right
+
+	q.list[pos] = newNode // insert new node at pos
+	q.nSize++
+}
+
+// Dequeue removes the last node from the queue.
+func (q *PriorityQueue[V]) Dequeue() {
+	if q.nSize == 0 {
+		return
+	}
+
+	q.list = q.list[:q.nSize-1]
+	q.nSize--
+}
+
+// Peek returns the value of the node with the highest priority in the queue.
+func (q *PriorityQueue[V]) Peek() V {
+	return q.list[q.nSize-1].Value()
+}
+
+// IsEmpty returns true if the queue is empty.
+func (q *PriorityQueue[V]) IsEmpty() bool {
+	return q.nSize == 0
+}
+
+// String returns a string representation of the queue.
+func (q *PriorityQueue[V]) String() string {
+	var s string
+
+	for idx, node := range q.list {
+		s += fmt.Sprintf("[%v]", node.value)
+		if idx != q.nSize-1 {
+			s += "-"
+		}
+	}
+
+	return s
+}
+
+// search returns the position of the node that is the closest to the given priority.
+func (q *PriorityQueue[V]) search(priority int) int {
+	low, high := 0, q.nSize
+	for low < high {
+		mid := int(uint(low+high) >> 1) // avoid overflow when computing mid
+
+		if q.list[mid].Priority() <= priority {
+			low = mid + 1
+		} else {
+			high = mid
+		}
+	}
+
+	return low
+}
diff --git a/queue/priority_queue_node.go b/queue/priority_queue_node.go
new file mode 100644
index 0000000..17b97fe
--- /dev/null
+++ b/queue/priority_queue_node.go
@@ -0,0 +1,31 @@
+package queue
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// PriorityQueueNode is a node in a priority queue.
+type PriorityQueueNode[V constraints.Ordered] struct {
+	value    V
+	priority int
+}
+
+// NewPriorityQueueNode returns a new priority queue node.
+func NewPriorityQueueNode[V constraints.Ordered](value V, priority int) PriorityQueueNode[V] {
+	return PriorityQueueNode[V]{value: value, priority: priority}
+}
+
+// Value returns the value of the node.
+func (node PriorityQueueNode[V]) Value() V {
+	return node.value
+}
+
+// Priority returns the priority of the node.
+func (node PriorityQueueNode[V]) Priority() int {
+	return node.priority
+}
+
+// SetPriority sets the priority of the node.
+func (node *PriorityQueueNode[V]) SetPriority(priority int) {
+	node.priority = priority
+}
diff --git a/queue/priority_queue_test.go b/queue/priority_queue_test.go
new file mode 100644
index 0000000..a315e6d
--- /dev/null
+++ b/queue/priority_queue_test.go
@@ -0,0 +1,52 @@
+package queue_test
+
+import (
+	"testing"
+
+	"github.com/HotPotatoC/sture/queue"
+)
+
+func TestPriorityQueue_Enqueue(t *testing.T) {
+	q := queue.NewPriorityQueue[string]()
+	exp := "[John]-[Adam]-[Bob]"
+
+	q.Enqueue("Adam", 3)
+	q.Enqueue("John", 2)
+	q.Enqueue("Bob", 5)
+
+	if q.IsEmpty() {
+		t.Error("Queue should not be empty")
+	}
+
+	if q.Peek() != "Bob" {
+		t.Errorf("Expected: Bob\nGot: %s", q.Peek())
+	}
+
+	if q.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, q.String())
+	}
+}
+
+func TestPriorityQueue_Dequeue(t *testing.T) {
+	q := queue.NewPriorityQueue[string]()
+
+	q.Enqueue("Adam", 3)
+	q.Enqueue("John", 2)
+	q.Enqueue("Bob", 5)
+
+	q.Dequeue()
+
+	exp := "[John]-[Adam]"
+
+	if q.IsEmpty() {
+		t.Error("Queue should not be empty")
+	}
+
+	if q.Peek() != "Adam" {
+		t.Errorf("Expected: Adam\nGot: %s", q.Peek())
+	}
+
+	if q.String() != exp {
+		t.Errorf("\nExpected: %s\nGot: %s", exp, q.String())
+	}
+}
diff --git a/queue/queue.go b/queue/queue.go
new file mode 100644
index 0000000..3eed6aa
--- /dev/null
+++ b/queue/queue.go
@@ -0,0 +1,44 @@
+package queue
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// Queue is a queue.
+type Queue[V constraints.Ordered] struct {
+	list  []V
+	nSize int
+}
+
+// NewQueue returns a new queue.
+func NewQueue[V constraints.Ordered]() *Queue[V] {
+	return &Queue[V]{
+		list: make([]V, 0),
+	}
+}
+
+// Enqueue adds a new node to the end of the queue.
+func (q *Queue[V]) Enqueue(value V) {
+	q.list = append(q.list, value)
+	q.nSize++
+}
+
+// Dequeue removes the first node from the queue.
+func (q *Queue[V]) Dequeue() {
+	if q.nSize == 0 {
+		return
+	}
+
+	q.list = q.list[1:]
+	q.nSize--
+}
+
+// Peek returns the value of the first node in the queue.
+func (q *Queue[V]) Peek() V {
+	return q.list[0]
+}
+
+// IsEmpty returns true if the queue is empty.
+func (q *Queue[V]) IsEmpty() bool {
+	return q.nSize == 0
+}
diff --git a/queue/queue_test.go b/queue/queue_test.go
new file mode 100644
index 0000000..1c6a642
--- /dev/null
+++ b/queue/queue_test.go
@@ -0,0 +1,41 @@
+package queue_test
+
+import (
+	"testing"
+
+	"github.com/HotPotatoC/sture/queue"
+)
+
+func TestQueue_Enqueue(t *testing.T) {
+	q := queue.NewQueue[string]()
+
+	q.Enqueue("Adam")
+	q.Enqueue("John")
+	q.Enqueue("Bob")
+
+	if q.IsEmpty() {
+		t.Error("Queue should not be empty")
+	}
+
+	if q.Peek() != "Adam" {
+		t.Errorf("Expected: Adam\nGot: %s", q.Peek())
+	}
+}
+
+func TestQueue_Dequeue(t *testing.T) {
+	q := queue.NewQueue[string]()
+
+	q.Enqueue("Adam")
+	q.Enqueue("John")
+	q.Enqueue("Bob")
+
+	q.Dequeue()
+
+	if q.IsEmpty() {
+		t.Error("Queue should not be empty")
+	}
+
+	if q.Peek() != "John" {
+		t.Errorf("Expected: John\nGot: %s", q.Peek())
+	}
+}
\ No newline at end of file
diff --git a/stack/stack.go b/stack/stack.go
new file mode 100644
index 0000000..a0f037f
--- /dev/null
+++ b/stack/stack.go
@@ -0,0 +1,38 @@
+package stack
+
+import (
+	"github.com/HotPotatoC/sture/linkedlist"
+	"golang.org/x/exp/constraints"
+)
+
+// Stack is a stack.
+type Stack[V constraints.Ordered] struct {
+	list *linkedlist.LinkedList[V]
+}
+
+// NewStack returns a new stack.
+func NewStack[V constraints.Ordered]() *Stack[V] {
+	return &Stack[V]{
+		list: linkedlist.NewLinkedList[V](),
+	}
+}
+
+// Add adds a new node to the top of the stack.
+func (s *Stack[V]) Add(value V) {
+	s.list.Append(value)
+}
+
+// Pop removes the top node from the stack.
+func (s *Stack[V]) Pop() {
+	s.list.Pop()
+}
+
+// Peek returns the value of the top node in the stack.
+func (s *Stack[V]) Peek() V {
+	return s.list.Head().Value()
+}
+
+// IsEmpty returns true if the stack is empty.
+func (s *Stack[V]) IsEmpty() bool {
+	return s.list.IsEmpty()
+}