Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
71: Implementing a Hash Table in Go
You've been using Go's built-in map since day one of this course. It's incredibly efficient, but it's also a "black box." To really understand how data structures work under the hood, we need to build one ourselves. Today, we're implementing a Hash Table from scratch.
The Mailbox Mental Model
Imagine a wall of 100 mailboxes in a lobby. You have a list of people (your keys) and the letters you want to give them (your values). You can't just throw letters randomly into boxes; you need a system to remember where you put them.
So, you create a rule: "Take the person's name, add up the alphabetical position of each letter, and divide by 100. The remainder is their mailbox number." This rule is your hash function. If "Alice" hashes to box 12, you put her letter there. When you need to find Alice's letter later, you don't search every box; you just run her name through the rule again, get 12, and go straight to that box.
But here's the problem: what if "Bob" also hashes to box 12? That's a collision. To fix this, you don't kick Alice out; you just put Bob's letter in the same box, sitting on top of Alice's. In software terms, we call this "chaining"—each mailbox actually holds a small list of entries.
Translating the Analogy to Go
To build this, we need three things: a way to store the entries, a way to handle those collisions, and the hashing logic itself. I'll use a simple slice of slices to represent our mailboxes and the lists inside them.
package main
import (
"fmt"
)
// Entry represents a single key-value pair
type Entry struct {
Key string
Value string
}
// HashTable represents the overall structure
type HashTable struct {
buckets [][]Entry
size int
}
// NewHashTable initializes a table with a fixed number of buckets
func NewHashTable(size int) *HashTable {
return &HashTable{
buckets: make([][]Entry, size),
size: size,
}
}
Turning Strings into Indices
In a production environment, you'd use a library like hash/fnv. But for this lesson, I want you to see the raw logic. We'll create a simple function that sums the bytes of the string. I've added a modulo operation at the end to ensure the result always fits within our slice bounds.
func (h *HashTable) hash(key string) int {
sum := 0
for _, char := range key {
sum += int(char)
}
return sum % h.size
}
Handling the Put and Get Operations
When we Put a value, we first find the bucket. If the key already exists in that bucket, we update the value. If not, we append a new entry. This is where that "chaining" from our mailbox analogy happens.
func (h *HashTable) Put(key, value string) {
index := h.hash(key)
bucket := h.buckets[index]
for i, entry := range bucket {
if entry.Key == key {
h.buckets[index][i].Value = value
return
}
}
h.buckets[index] = append(bucket, Entry{Key: key, Value: value})
}
func (h *HashTable) Get(key string) (string, bool) {
index := h.hash(key)
bucket := h.buckets[index]
for _, entry := range bucket {
if entry.Key == key {
return entry.Value, true
}
}
return "", false
}
Why This Matters
You might be thinking, "Why bother when map[string]string exists?" Because understanding this helps you understand time complexity. In a perfect world, a Hash Table gives us O(1) access—constant time. But if your hash function is bad and every single key ends up in the same bucket, your Hash Table effectively becomes a linked list, and performance drops to O(n). When you see a performance degradation in a real-world Go app, it's often because a map is dealing with too many collisions or is triggering an expensive resize operation.
📋 Practical Task
Build a Session Token Store
Using the logic from this lesson, implement a specialized SessionStore. Your task is to build a hash table that stores user session tokens. The requirements are:
- Create a
SessionStorestruct with a bucket size of 16. - Implement a
SaveSession(token string, userID int)method. Note that the value here is anint, not astring. - Implement a
GetUserID(token string) (int, bool)method to retrieve the user associated with a token. - Implement a
DeleteSession(token string)method that removes an entry from the bucket if it exists. Hint: You'll need to slice out the element from the bucket slice.
Test your implementation by saving three different tokens, deleting one, and verifying that the others are still accessible.
There are no comments for now.