> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Your Own Redis

> Master networking, protocols, and data structures by building Redis from scratch in Go

![RESP Protocol](https://mintcdn.com/devweeekends/AEOaWh79Ur7CdHHv/images/courses/build-your-own-x/redis-resp.svg?fit=max\&auto=format\&n=AEOaWh79Ur7CdHHv\&q=85\&s=377d9f4a70acb77690c8ff03d6b53e6f)

# Build Your Own Redis

**Target Audience**: Mid-Level Engineers (2-5 years experience)\
**Language**: Go (with Java & JavaScript alternatives)\
**Duration**: 3-4 weeks\
**Difficulty**: ⭐⭐⭐⭐☆

***

## Why Build Redis?

Redis is the **world's most popular in-memory data store**, used by companies from tiny startups to Netflix, Twitter, and GitHub. By building your own, you'll master:

* **Network programming** -- TCP servers, connection handling, and the realities of partial reads and connection lifecycle
* **Protocol design** -- RESP (Redis Serialization Protocol) parsing, and why protocol simplicity is a feature, not a limitation
* **Data structures at scale** -- Hash tables, skip lists, doubly-linked lists, and when to choose each one
* **Persistence strategies** -- RDB snapshots vs. AOF logging, and the durability-performance trade-off that every database must make
* **Concurrent programming** -- Goroutines, channels, mutexes, and the art of keeping shared state consistent without destroying throughput

> [!WARNING]
>
> This is an advanced project. You should be comfortable with TCP/IP basics (what a socket is, how connections are established) and have experience with at least one systems programming language. If `net.Listen("tcp", ":6379")` looks unfamiliar, review the Go networking tutorial first.

***

## Redis Architecture Overview

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           REDIS ARCHITECTURE                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   CLIENT                     SERVER                      STORAGE            │
│   ──────                     ──────                      ───────            │
│                                                                              │
│   ┌──────────┐    RESP     ┌─────────────────┐        ┌───────────┐        │
│   │  redis   │◄───────────►│  Command Router │        │    RDB    │        │
│   │  -cli    │  Protocol   │                 │◄──────►│  Snapshot │        │
│   └──────────┘             │  ┌───────────┐  │        └───────────┘        │
│                            │  │   GET     │  │                              │
│   ┌──────────┐             │  │   SET     │  │        ┌───────────┐        │
│   │   App    │◄───────────►│  │   DEL     │  │◄──────►│    AOF    │        │
│   │  Client  │             │  │   ...     │  │        │  Append   │        │
│   └──────────┘             │  └───────────┘  │        └───────────┘        │
│                            │        │        │                              │
│                            │   ┌────▼────┐   │                              │
│                            │   │  Data   │   │                              │
│                            │   │  Store  │   │                              │
│                            │   │ (HashMap)│   │                              │
│                            │   └─────────┘   │                              │
│                            └─────────────────┘                              │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

***

## What You'll Build

### Core Features

| Feature | Description | Skills Learned |
| - | - | - |
| **RESP Parser** | Parse/serialize Redis protocol | Protocol design, binary parsing |
| **TCP Server** | Handle multiple clients | Network programming, concurrency |
| **String Commands** | GET, SET, APPEND, INCR | Basic key-value operations |
| **Expiration** | EXPIRE, TTL, PTTL | Timer management, lazy deletion |
| **List Commands** | LPUSH, RPUSH, LPOP, LRANGE | Linked list implementation |
| **Set Commands** | SADD, SMEMBERS, SINTER | Set operations |
| **Hash Commands** | HSET, HGET, HGETALL | Nested hash tables |
| **Sorted Sets** | ZADD, ZRANGE, ZRANK | Skip list implementation |
| **Pub/Sub** | SUBSCRIBE, PUBLISH | Event-driven patterns |
| **Persistence** | RDB, AOF | Durability, recovery |

***

## Implementation: Go

### Project Structure

```
myredis/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── protocol/
│   │   ├── parser.go
│   │   ├── writer.go
│   │   └── types.go
│   ├── server/
│   │   ├── server.go
│   │   ├── client.go
│   │   └── handler.go
│   ├── store/
│   │   ├── store.go
│   │   ├── string.go
│   │   ├── list.go
│   │   ├── set.go
│   │   ├── hash.go
│   │   ├── zset.go
│   │   └── expiry.go
│   ├── persistence/
│   │   ├── rdb.go
│   │   └── aof.go
│   └── pubsub/
│       └── pubsub.go
├── go.mod
└── README.md
```

### Core Implementation

```go internal/protocol/types.go theme={null}
package protocol

// RESP (Redis Serialization Protocol) types
// https://redis.io/docs/reference/protocol-spec/

type RESPType byte

const (
    SimpleString RESPType = '+' // +OK\r\n
    Error        RESPType = '-' // -ERR message\r\n
    Integer      RESPType = ':' // :1000\r\n
    BulkString   RESPType = '$' // $5\r\nhello\r\n
    Array        RESPType = '*' // *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
)

// Value represents a RESP value
type Value struct {
    Type  RESPType
    Str   string
    Num   int64
    Bulk  []byte
    Array []Value
    Null  bool
}

// Common responses
var (
    OKResponse   = Value{Type: SimpleString, Str: "OK"}
    NullResponse = Value{Type: BulkString, Null: true}
    PongResponse = Value{Type: SimpleString, Str: "PONG"}
)

// NewError creates an error response
func NewError(msg string) Value {
    return Value{Type: Error, Str: msg}
}

// NewInteger creates an integer response
func NewInteger(n int64) Value {
    return Value{Type: Integer, Num: n}
}

// NewBulkString creates a bulk string response
func NewBulkString(s string) Value {
    return Value{Type: BulkString, Bulk: []byte(s)}
}

// NewArray creates an array response
func NewArray(vals []Value) Value {
    return Value{Type: Array, Array: vals}
}
```

```go internal/protocol/parser.go theme={null}
package protocol

import (
    "bufio"
    "errors"
    "io"
    "strconv"
)

var (
    ErrInvalidSyntax = errors.New("invalid RESP syntax")
    ErrUnexpectedEOF = errors.New("unexpected end of input")
)

// Parser reads RESP values from a buffered reader
type Parser struct {
    reader *bufio.Reader
}

// NewParser creates a new RESP parser
func NewParser(r io.Reader) *Parser {
    return &Parser{
        reader: bufio.NewReader(r),
    }
}

// Parse reads and parses a single RESP value
func (p *Parser) Parse() (Value, error) {
    typeByte, err := p.reader.ReadByte()
    if err != nil {
        return Value{}, err
    }

    switch RESPType(typeByte) {
    case SimpleString:
        return p.parseSimpleString()
    case Error:
        return p.parseError()
    case Integer:
        return p.parseInteger()
    case BulkString:
        return p.parseBulkString()
    case Array:
        return p.parseArray()
    default:
        // Handle inline commands (for redis-cli without RESP)
        p.reader.UnreadByte()
        return p.parseInlineCommand()
    }
}

func (p *Parser) readLine() (string, error) {
    line, err := p.reader.ReadString('\n')
    if err != nil {
        return "", err
    }
    // Remove \r\n
    if len(line) >= 2 && line[len(line)-2] == '\r' {
        return line[:len(line)-2], nil
    }
    return line[:len(line)-1], nil
}

func (p *Parser) parseSimpleString() (Value, error) {
    line, err := p.readLine()
    if err != nil {
        return Value{}, err
    }
    return Value{Type: SimpleString, Str: line}, nil
}

func (p *Parser) parseError() (Value, error) {
    line, err := p.readLine()
    if err != nil {
        return Value{}, err
    }
    return Value{Type: Error, Str: line}, nil
}

func (p *Parser) parseInteger() (Value, error) {
    line, err := p.readLine()
    if err != nil {
        return Value{}, err
    }
    n, err := strconv.ParseInt(line, 10, 64)
    if err != nil {
        return Value{}, ErrInvalidSyntax
    }
    return Value{Type: Integer, Num: n}, nil
}

func (p *Parser) parseBulkString() (Value, error) {
    line, err := p.readLine()
    if err != nil {
        return Value{}, err
    }

    length, err := strconv.Atoi(line)
    if err != nil {
        return Value{}, ErrInvalidSyntax
    }

    // Null bulk string
    if length == -1 {
        return Value{Type: BulkString, Null: true}, nil
    }

    // Read exactly 'length' bytes plus \r\n
    buf := make([]byte, length+2)
    _, err = io.ReadFull(p.reader, buf)
    if err != nil {
        return Value{}, ErrUnexpectedEOF
    }

    return Value{Type: BulkString, Bulk: buf[:length]}, nil
}

func (p *Parser) parseArray() (Value, error) {
    line, err := p.readLine()
    if err != nil {
        return Value{}, err
    }

    count, err := strconv.Atoi(line)
    if err != nil {
        return Value{}, ErrInvalidSyntax
    }

    // Null array
    if count == -1 {
        return Value{Type: Array, Null: true}, nil
    }

    array := make([]Value, count)
    for i := 0; i < count; i++ {
        val, err := p.Parse()
        if err != nil {
            return Value{}, err
        }
        array[i] = val
    }

    return Value{Type: Array, Array: array}, nil
}

func (p *Parser) parseInlineCommand() (Value, error) {
    line, err := p.readLine()
    if err != nil {
        return Value{}, err
    }

    // Split by spaces and convert to array
    parts := splitInlineCommand(line)
    array := make([]Value, len(parts))
    for i, part := range parts {
        array[i] = Value{Type: BulkString, Bulk: []byte(part)}
    }

    return Value{Type: Array, Array: array}, nil
}

func splitInlineCommand(line string) []string {
    var parts []string
    var current []byte
    inQuote := false
    quoteChar := byte(0)

    for i := 0; i < len(line); i++ {
        c := line[i]

        if inQuote {
            if c == quoteChar {
                inQuote = false
            } else {
                current = append(current, c)
            }
        } else {
            if c == '"' || c == '\'' {
                inQuote = true
                quoteChar = c
            } else if c == ' ' {
                if len(current) > 0 {
                    parts = append(parts, string(current))
                    current = nil
                }
            } else {
                current = append(current, c)
            }
        }
    }

    if len(current) > 0 {
        parts = append(parts, string(current))
    }

    return parts
}
```

```go internal/protocol/writer.go theme={null}
package protocol

import (
    "bufio"
    "io"
    "strconv"
)

// Writer writes RESP values to a buffered writer
type Writer struct {
    writer *bufio.Writer
}

// NewWriter creates a new RESP writer
func NewWriter(w io.Writer) *Writer {
    return &Writer{
        writer: bufio.NewWriter(w),
    }
}

// Write serializes a Value to RESP format and writes it
func (w *Writer) Write(v Value) error {
    switch v.Type {
    case SimpleString:
        return w.writeSimpleString(v.Str)
    case Error:
        return w.writeError(v.Str)
    case Integer:
        return w.writeInteger(v.Num)
    case BulkString:
        return w.writeBulkString(v)
    case Array:
        return w.writeArray(v)
    default:
        return nil
    }
}

// Flush flushes the underlying buffer
func (w *Writer) Flush() error {
    return w.writer.Flush()
}

func (w *Writer) writeSimpleString(s string) error {
    _, err := w.writer.WriteString("+" + s + "\r\n")
    return err
}

func (w *Writer) writeError(s string) error {
    _, err := w.writer.WriteString("-" + s + "\r\n")
    return err
}

func (w *Writer) writeInteger(n int64) error {
    _, err := w.writer.WriteString(":" + strconv.FormatInt(n, 10) + "\r\n")
    return err
}

func (w *Writer) writeBulkString(v Value) error {
    if v.Null {
        _, err := w.writer.WriteString("$-1\r\n")
        return err
    }

    length := len(v.Bulk)
    w.writer.WriteString("$" + strconv.Itoa(length) + "\r\n")
    w.writer.Write(v.Bulk)
    _, err := w.writer.WriteString("\r\n")
    return err
}

func (w *Writer) writeArray(v Value) error {
    if v.Null {
        _, err := w.writer.WriteString("*-1\r\n")
        return err
    }

    length := len(v.Array)
    w.writer.WriteString("*" + strconv.Itoa(length) + "\r\n")

    for _, elem := range v.Array {
        if err := w.Write(elem); err != nil {
            return err
        }
    }

    return nil
}
```

```go internal/store/store.go theme={null}
package store

import (
    "sync"
    "time"
)

// Store is the main data store
type Store struct {
    data    map[string]*Entry
    expires map[string]time.Time
    mu      sync.RWMutex
}

// Entry represents a stored value with its type
type Entry struct {
    Type  DataType
    Value interface{}
}

// DataType represents the type of stored data
type DataType int

const (
    TypeString DataType = iota
    TypeList
    TypeSet
    TypeHash
    TypeZSet
)

// NewStore creates a new data store
func NewStore() *Store {
    s := &Store{
        data:    make(map[string]*Entry),
        expires: make(map[string]time.Time),
    }

    // Start background expiry goroutine
    go s.expireLoop()

    return s
}

// Get retrieves a value by key
func (s *Store) Get(key string) (*Entry, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    // Check expiration
    if exp, ok := s.expires[key]; ok && time.Now().After(exp) {
        return nil, false
    }

    entry, ok := s.data[key]
    return entry, ok
}

// Set stores a value
func (s *Store) Set(key string, entry *Entry) {
    s.mu.Lock()
    defer s.mu.Unlock()

    s.data[key] = entry
    delete(s.expires, key) // Clear any existing expiration
}

// SetWithExpiry stores a value with an expiration time
func (s *Store) SetWithExpiry(key string, entry *Entry, expiry time.Duration) {
    s.mu.Lock()
    defer s.mu.Unlock()

    s.data[key] = entry
    s.expires[key] = time.Now().Add(expiry)
}

// Delete removes a key
func (s *Store) Delete(keys ...string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    var count int64
    for _, key := range keys {
        if _, ok := s.data[key]; ok {
            delete(s.data, key)
            delete(s.expires, key)
            count++
        }
    }
    return count
}

// Exists checks if keys exist
func (s *Store) Exists(keys ...string) int64 {
    s.mu.RLock()
    defer s.mu.RUnlock()

    var count int64
    for _, key := range keys {
        if exp, ok := s.expires[key]; ok && time.Now().After(exp) {
            continue
        }
        if _, ok := s.data[key]; ok {
            count++
        }
    }
    return count
}

// Expire sets an expiration on a key
func (s *Store) Expire(key string, duration time.Duration) bool {
    s.mu.Lock()
    defer s.mu.Unlock()

    if _, ok := s.data[key]; !ok {
        return false
    }

    s.expires[key] = time.Now().Add(duration)
    return true
}

// TTL returns the remaining time to live
func (s *Store) TTL(key string) int64 {
    s.mu.RLock()
    defer s.mu.RUnlock()

    if _, ok := s.data[key]; !ok {
        return -2 // Key doesn't exist
    }

    exp, ok := s.expires[key]
    if !ok {
        return -1 // No expiration
    }

    remaining := time.Until(exp)
    if remaining < 0 {
        return -2 // Expired
    }

    return int64(remaining.Seconds())
}

// Keys returns all keys matching a pattern
func (s *Store) Keys(pattern string) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    var keys []string
    now := time.Now()

    for key := range s.data {
        // Check expiration
        if exp, ok := s.expires[key]; ok && now.After(exp) {
            continue
        }

        // Simple pattern matching (* matches everything)
        if pattern == "*" || matchPattern(pattern, key) {
            keys = append(keys, key)
        }
    }

    return keys
}

// Background expiry cleanup
func (s *Store) expireLoop() {
    ticker := time.NewTicker(100 * time.Millisecond)
    defer ticker.Stop()

    for range ticker.C {
        s.mu.Lock()
        now := time.Now()
        
        // Sample and delete expired keys
        count := 0
        for key, exp := range s.expires {
            if now.After(exp) {
                delete(s.data, key)
                delete(s.expires, key)
            }
            count++
            if count > 20 { // Limit per iteration
                break
            }
        }
        s.mu.Unlock()
    }
}

// Simple glob pattern matching
func matchPattern(pattern, s string) bool {
    if pattern == "*" {
        return true
    }
    // Simplified - you'd want a proper glob implementation
    // This just handles prefix* and *suffix patterns
    if len(pattern) > 0 && pattern[0] == '*' {
        return len(s) >= len(pattern)-1 && s[len(s)-(len(pattern)-1):] == pattern[1:]
    }
    if len(pattern) > 0 && pattern[len(pattern)-1] == '*' {
        return len(s) >= len(pattern)-1 && s[:len(pattern)-1] == pattern[:len(pattern)-1]
    }
    return pattern == s
}
```

```go internal/store/string.go theme={null}
package store

import (
    "strconv"
)

// String operations

// GetString retrieves a string value
func (s *Store) GetString(key string) (string, bool) {
    entry, ok := s.Get(key)
    if !ok {
        return "", false
    }

    if entry.Type != TypeString {
        return "", false
    }

    return entry.Value.(string), true
}

// SetString stores a string value
func (s *Store) SetString(key, value string) {
    s.Set(key, &Entry{
        Type:  TypeString,
        Value: value,
    })
}

// Append appends to a string value
func (s *Store) Append(key, value string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok {
        s.data[key] = &Entry{Type: TypeString, Value: value}
        return int64(len(value))
    }

    if entry.Type != TypeString {
        return -1
    }

    newValue := entry.Value.(string) + value
    entry.Value = newValue
    return int64(len(newValue))
}

// Incr increments a string value as integer
func (s *Store) Incr(key string) (int64, error) {
    return s.IncrBy(key, 1)
}

// IncrBy increments by a specified amount
func (s *Store) IncrBy(key string, delta int64) (int64, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok {
        s.data[key] = &Entry{Type: TypeString, Value: strconv.FormatInt(delta, 10)}
        return delta, nil
    }

    if entry.Type != TypeString {
        return 0, ErrWrongType
    }

    current, err := strconv.ParseInt(entry.Value.(string), 10, 64)
    if err != nil {
        return 0, ErrNotInteger
    }

    newValue := current + delta
    entry.Value = strconv.FormatInt(newValue, 10)
    return newValue, nil
}

// Decr decrements a string value
func (s *Store) Decr(key string) (int64, error) {
    return s.IncrBy(key, -1)
}

// MGet gets multiple keys
func (s *Store) MGet(keys ...string) []interface{} {
    s.mu.RLock()
    defer s.mu.RUnlock()

    results := make([]interface{}, len(keys))
    for i, key := range keys {
        entry, ok := s.data[key]
        if !ok || entry.Type != TypeString {
            results[i] = nil
            continue
        }
        results[i] = entry.Value.(string)
    }
    return results
}

// MSet sets multiple key-value pairs
func (s *Store) MSet(pairs map[string]string) {
    s.mu.Lock()
    defer s.mu.Unlock()

    for key, value := range pairs {
        s.data[key] = &Entry{Type: TypeString, Value: value}
    }
}

// Errors
type StoreError string

func (e StoreError) Error() string { return string(e) }

const (
    ErrWrongType  = StoreError("WRONGTYPE Operation against a key holding the wrong kind of value")
    ErrNotInteger = StoreError("ERR value is not an integer or out of range")
)
```

```go internal/store/list.go theme={null}
package store

import "container/list"

// List operations using Go's container/list

// LPush inserts values at the head of the list
func (s *Store) LPush(key string, values ...string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok {
        l := list.New()
        entry = &Entry{Type: TypeList, Value: l}
        s.data[key] = entry
    }

    if entry.Type != TypeList {
        return -1
    }

    l := entry.Value.(*list.List)
    for _, v := range values {
        l.PushFront(v)
    }

    return int64(l.Len())
}

// RPush inserts values at the tail of the list
func (s *Store) RPush(key string, values ...string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok {
        l := list.New()
        entry = &Entry{Type: TypeList, Value: l}
        s.data[key] = entry
    }

    if entry.Type != TypeList {
        return -1
    }

    l := entry.Value.(*list.List)
    for _, v := range values {
        l.PushBack(v)
    }

    return int64(l.Len())
}

// LPop removes and returns the first element
func (s *Store) LPop(key string) (string, bool) {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeList {
        return "", false
    }

    l := entry.Value.(*list.List)
    if l.Len() == 0 {
        return "", false
    }

    elem := l.Front()
    l.Remove(elem)

    // Clean up empty list
    if l.Len() == 0 {
        delete(s.data, key)
    }

    return elem.Value.(string), true
}

// RPop removes and returns the last element
func (s *Store) RPop(key string) (string, bool) {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeList {
        return "", false
    }

    l := entry.Value.(*list.List)
    if l.Len() == 0 {
        return "", false
    }

    elem := l.Back()
    l.Remove(elem)

    if l.Len() == 0 {
        delete(s.data, key)
    }

    return elem.Value.(string), true
}

// LRange returns a range of elements
func (s *Store) LRange(key string, start, stop int64) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeList {
        return nil
    }

    l := entry.Value.(*list.List)
    length := int64(l.Len())

    // Handle negative indices
    if start < 0 {
        start = length + start
    }
    if stop < 0 {
        stop = length + stop
    }

    // Bounds checking
    if start < 0 {
        start = 0
    }
    if stop >= length {
        stop = length - 1
    }
    if start > stop || start >= length {
        return nil
    }

    var result []string
    var i int64 = 0
    for elem := l.Front(); elem != nil && i <= stop; elem = elem.Next() {
        if i >= start {
            result = append(result, elem.Value.(string))
        }
        i++
    }

    return result
}

// LLen returns the length of the list
func (s *Store) LLen(key string) int64 {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeList {
        return 0
    }

    return int64(entry.Value.(*list.List).Len())
}

// LIndex returns the element at index
func (s *Store) LIndex(key string, index int64) (string, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeList {
        return "", false
    }

    l := entry.Value.(*list.List)
    length := int64(l.Len())

    // Handle negative index
    if index < 0 {
        index = length + index
    }

    if index < 0 || index >= length {
        return "", false
    }

    var i int64 = 0
    for elem := l.Front(); elem != nil; elem = elem.Next() {
        if i == index {
            return elem.Value.(string), true
        }
        i++
    }

    return "", false
}
```

```go internal/store/hash.go theme={null}
package store

// Hash operations

// HSet sets field in hash to value
func (s *Store) HSet(key string, fields map[string]string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok {
        h := make(map[string]string)
        entry = &Entry{Type: TypeHash, Value: h}
        s.data[key] = entry
    }

    if entry.Type != TypeHash {
        return -1
    }

    h := entry.Value.(map[string]string)
    var newFields int64 = 0

    for field, value := range fields {
        if _, exists := h[field]; !exists {
            newFields++
        }
        h[field] = value
    }

    return newFields
}

// HGet gets a field from hash
func (s *Store) HGet(key, field string) (string, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeHash {
        return "", false
    }

    h := entry.Value.(map[string]string)
    value, ok := h[field]
    return value, ok
}

// HGetAll gets all fields and values
func (s *Store) HGetAll(key string) map[string]string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeHash {
        return nil
    }

    h := entry.Value.(map[string]string)
    result := make(map[string]string, len(h))
    for k, v := range h {
        result[k] = v
    }

    return result
}

// HDel deletes fields from hash
func (s *Store) HDel(key string, fields ...string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeHash {
        return 0
    }

    h := entry.Value.(map[string]string)
    var deleted int64 = 0

    for _, field := range fields {
        if _, exists := h[field]; exists {
            delete(h, field)
            deleted++
        }
    }

    // Clean up empty hash
    if len(h) == 0 {
        delete(s.data, key)
    }

    return deleted
}

// HExists checks if field exists
func (s *Store) HExists(key, field string) bool {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeHash {
        return false
    }

    h := entry.Value.(map[string]string)
    _, exists := h[field]
    return exists
}

// HLen returns number of fields in hash
func (s *Store) HLen(key string) int64 {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeHash {
        return 0
    }

    h := entry.Value.(map[string]string)
    return int64(len(h))
}

// HKeys returns all field names
func (s *Store) HKeys(key string) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeHash {
        return nil
    }

    h := entry.Value.(map[string]string)
    keys := make([]string, 0, len(h))
    for k := range h {
        keys = append(keys, k)
    }

    return keys
}

// HVals returns all values
func (s *Store) HVals(key string) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeHash {
        return nil
    }

    h := entry.Value.(map[string]string)
    vals := make([]string, 0, len(h))
    for _, v := range h {
        vals = append(vals, v)
    }

    return vals
}
```

```go internal/store/set.go theme={null}
package store

// Set operations

// SAdd adds members to a set
func (s *Store) SAdd(key string, members ...string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok {
        set := make(map[string]struct{})
        entry = &Entry{Type: TypeSet, Value: set}
        s.data[key] = entry
    }

    if entry.Type != TypeSet {
        return -1
    }

    set := entry.Value.(map[string]struct{})
    var added int64 = 0

    for _, member := range members {
        if _, exists := set[member]; !exists {
            set[member] = struct{}{}
            added++
        }
    }

    return added
}

// SMembers returns all members of a set
func (s *Store) SMembers(key string) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeSet {
        return nil
    }

    set := entry.Value.(map[string]struct{})
    members := make([]string, 0, len(set))
    for member := range set {
        members = append(members, member)
    }

    return members
}

// SIsMember checks if member exists in set
func (s *Store) SIsMember(key, member string) bool {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeSet {
        return false
    }

    set := entry.Value.(map[string]struct{})
    _, exists := set[member]
    return exists
}

// SCard returns the cardinality (size) of set
func (s *Store) SCard(key string) int64 {
    s.mu.RLock()
    defer s.mu.RUnlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeSet {
        return 0
    }

    return int64(len(entry.Value.(map[string]struct{})))
}

// SRem removes members from set
func (s *Store) SRem(key string, members ...string) int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    entry, ok := s.data[key]
    if !ok || entry.Type != TypeSet {
        return 0
    }

    set := entry.Value.(map[string]struct{})
    var removed int64 = 0

    for _, member := range members {
        if _, exists := set[member]; exists {
            delete(set, member)
            removed++
        }
    }

    if len(set) == 0 {
        delete(s.data, key)
    }

    return removed
}

// SInter returns intersection of sets
func (s *Store) SInter(keys ...string) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    if len(keys) == 0 {
        return nil
    }

    // Get first set
    entry, ok := s.data[keys[0]]
    if !ok || entry.Type != TypeSet {
        return nil
    }

    result := make(map[string]struct{})
    for member := range entry.Value.(map[string]struct{}) {
        result[member] = struct{}{}
    }

    // Intersect with remaining sets
    for _, key := range keys[1:] {
        entry, ok := s.data[key]
        if !ok || entry.Type != TypeSet {
            return nil
        }

        set := entry.Value.(map[string]struct{})
        for member := range result {
            if _, exists := set[member]; !exists {
                delete(result, member)
            }
        }
    }

    members := make([]string, 0, len(result))
    for member := range result {
        members = append(members, member)
    }

    return members
}

// SUnion returns union of sets
func (s *Store) SUnion(keys ...string) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    result := make(map[string]struct{})

    for _, key := range keys {
        entry, ok := s.data[key]
        if !ok || entry.Type != TypeSet {
            continue
        }

        set := entry.Value.(map[string]struct{})
        for member := range set {
            result[member] = struct{}{}
        }
    }

    members := make([]string, 0, len(result))
    for member := range result {
        members = append(members, member)
    }

    return members
}

// SDiff returns difference of sets (first - rest)
func (s *Store) SDiff(keys ...string) []string {
    s.mu.RLock()
    defer s.mu.RUnlock()

    if len(keys) == 0 {
        return nil
    }

    entry, ok := s.data[keys[0]]
    if !ok || entry.Type != TypeSet {
        return nil
    }

    result := make(map[string]struct{})
    for member := range entry.Value.(map[string]struct{}) {
        result[member] = struct{}{}
    }

    for _, key := range keys[1:] {
        entry, ok := s.data[key]
        if !ok || entry.Type != TypeSet {
            continue
        }

        set := entry.Value.(map[string]struct{})
        for member := range set {
            delete(result, member)
        }
    }

    members := make([]string, 0, len(result))
    for member := range result {
        members = append(members, member)
    }

    return members
}
```

```go internal/server/server.go theme={null}
package server

import (
    "fmt"
    "log"
    "net"
    "sync"

    "myredis/internal/store"
)

// Server represents the Redis server
type Server struct {
    listener net.Listener
    store    *store.Store
    clients  map[*Client]struct{}
    mu       sync.Mutex
    quit     chan struct{}
}

// New creates a new server
func New(addr string) (*Server, error) {
    listener, err := net.Listen("tcp", addr)
    if err != nil {
        return nil, fmt.Errorf("failed to listen: %w", err)
    }

    return &Server{
        listener: listener,
        store:    store.NewStore(),
        clients:  make(map[*Client]struct{}),
        quit:     make(chan struct{}),
    }, nil
}

// Start begins accepting connections
func (s *Server) Start() error {
    log.Printf("Server listening on %s", s.listener.Addr())

    for {
        conn, err := s.listener.Accept()
        if err != nil {
            select {
            case <-s.quit:
                return nil
            default:
                log.Printf("Accept error: %v", err)
                continue
            }
        }

        client := NewClient(conn, s)
        s.addClient(client)
        go s.handleClient(client)
    }
}

// Stop gracefully shuts down the server
func (s *Server) Stop() {
    close(s.quit)
    s.listener.Close()

    s.mu.Lock()
    for client := range s.clients {
        client.Close()
    }
    s.mu.Unlock()
}

func (s *Server) addClient(c *Client) {
    s.mu.Lock()
    s.clients[c] = struct{}{}
    s.mu.Unlock()
}

func (s *Server) removeClient(c *Client) {
    s.mu.Lock()
    delete(s.clients, c)
    s.mu.Unlock()
}

func (s *Server) handleClient(c *Client) {
    defer func() {
        c.Close()
        s.removeClient(c)
    }()

    c.Handle()
}

// Store returns the data store
func (s *Server) Store() *store.Store {
    return s.store
}
```

```go internal/server/client.go theme={null}
package server

import (
    "io"
    "log"
    "net"
    "strings"

    "myredis/internal/protocol"
)

// Client represents a connected client
type Client struct {
    conn   net.Conn
    server *Server
    parser *protocol.Parser
    writer *protocol.Writer
}

// NewClient creates a new client
func NewClient(conn net.Conn, server *Server) *Client {
    return &Client{
        conn:   conn,
        server: server,
        parser: protocol.NewParser(conn),
        writer: protocol.NewWriter(conn),
    }
}

// Handle processes client commands
func (c *Client) Handle() {
    for {
        value, err := c.parser.Parse()
        if err != nil {
            if err != io.EOF {
                log.Printf("Parse error: %v", err)
            }
            return
        }

        response := c.executeCommand(value)
        if err := c.writer.Write(response); err != nil {
            log.Printf("Write error: %v", err)
            return
        }
        c.writer.Flush()
    }
}

// Close closes the client connection
func (c *Client) Close() {
    c.conn.Close()
}

func (c *Client) executeCommand(value protocol.Value) protocol.Value {
    if value.Type != protocol.Array || len(value.Array) == 0 {
        return protocol.NewError("ERR unknown command")
    }

    // Extract command and arguments
    args := make([]string, len(value.Array))
    for i, v := range value.Array {
        args[i] = string(v.Bulk)
    }

    cmd := strings.ToUpper(args[0])
    args = args[1:]

    return c.handleCommand(cmd, args)
}

func (c *Client) handleCommand(cmd string, args []string) protocol.Value {
    store := c.server.Store()

    switch cmd {
    // Connection commands
    case "PING":
        if len(args) == 0 {
            return protocol.PongResponse
        }
        return protocol.NewBulkString(args[0])

    case "ECHO":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'echo' command")
        }
        return protocol.NewBulkString(args[0])

    // String commands
    case "GET":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'get' command")
        }
        value, ok := store.GetString(args[0])
        if !ok {
            return protocol.NullResponse
        }
        return protocol.NewBulkString(value)

    case "SET":
        if len(args) < 2 {
            return protocol.NewError("ERR wrong number of arguments for 'set' command")
        }
        store.SetString(args[0], args[1])
        return protocol.OKResponse

    case "DEL":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'del' command")
        }
        count := store.Delete(args...)
        return protocol.NewInteger(count)

    case "EXISTS":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'exists' command")
        }
        count := store.Exists(args...)
        return protocol.NewInteger(count)

    case "INCR":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'incr' command")
        }
        n, err := store.Incr(args[0])
        if err != nil {
            return protocol.NewError(err.Error())
        }
        return protocol.NewInteger(n)

    case "DECR":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'decr' command")
        }
        n, err := store.Decr(args[0])
        if err != nil {
            return protocol.NewError(err.Error())
        }
        return protocol.NewInteger(n)

    // List commands
    case "LPUSH":
        if len(args) < 2 {
            return protocol.NewError("ERR wrong number of arguments for 'lpush' command")
        }
        n := store.LPush(args[0], args[1:]...)
        if n < 0 {
            return protocol.NewError(string(store.ErrWrongType))
        }
        return protocol.NewInteger(n)

    case "RPUSH":
        if len(args) < 2 {
            return protocol.NewError("ERR wrong number of arguments for 'rpush' command")
        }
        n := store.RPush(args[0], args[1:]...)
        if n < 0 {
            return protocol.NewError(string(store.ErrWrongType))
        }
        return protocol.NewInteger(n)

    case "LPOP":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'lpop' command")
        }
        v, ok := store.LPop(args[0])
        if !ok {
            return protocol.NullResponse
        }
        return protocol.NewBulkString(v)

    case "RPOP":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'rpop' command")
        }
        v, ok := store.RPop(args[0])
        if !ok {
            return protocol.NullResponse
        }
        return protocol.NewBulkString(v)

    case "LRANGE":
        if len(args) < 3 {
            return protocol.NewError("ERR wrong number of arguments for 'lrange' command")
        }
        start := parseInt(args[1])
        stop := parseInt(args[2])
        values := store.LRange(args[0], start, stop)
        return toArrayResponse(values)

    case "LLEN":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'llen' command")
        }
        return protocol.NewInteger(store.LLen(args[0]))

    // Hash commands
    case "HSET":
        if len(args) < 3 || len(args)%2 == 0 {
            return protocol.NewError("ERR wrong number of arguments for 'hset' command")
        }
        fields := make(map[string]string)
        for i := 1; i < len(args); i += 2 {
            fields[args[i]] = args[i+1]
        }
        n := store.HSet(args[0], fields)
        if n < 0 {
            return protocol.NewError(string(store.ErrWrongType))
        }
        return protocol.NewInteger(n)

    case "HGET":
        if len(args) < 2 {
            return protocol.NewError("ERR wrong number of arguments for 'hget' command")
        }
        v, ok := store.HGet(args[0], args[1])
        if !ok {
            return protocol.NullResponse
        }
        return protocol.NewBulkString(v)

    case "HGETALL":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'hgetall' command")
        }
        h := store.HGetAll(args[0])
        result := make([]protocol.Value, 0, len(h)*2)
        for k, v := range h {
            result = append(result, protocol.NewBulkString(k))
            result = append(result, protocol.NewBulkString(v))
        }
        return protocol.NewArray(result)

    case "HDEL":
        if len(args) < 2 {
            return protocol.NewError("ERR wrong number of arguments for 'hdel' command")
        }
        n := store.HDel(args[0], args[1:]...)
        return protocol.NewInteger(n)

    // Set commands
    case "SADD":
        if len(args) < 2 {
            return protocol.NewError("ERR wrong number of arguments for 'sadd' command")
        }
        n := store.SAdd(args[0], args[1:]...)
        if n < 0 {
            return protocol.NewError(string(store.ErrWrongType))
        }
        return protocol.NewInteger(n)

    case "SMEMBERS":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'smembers' command")
        }
        return toArrayResponse(store.SMembers(args[0]))

    case "SISMEMBER":
        if len(args) < 2 {
            return protocol.NewError("ERR wrong number of arguments for 'sismember' command")
        }
        if store.SIsMember(args[0], args[1]) {
            return protocol.NewInteger(1)
        }
        return protocol.NewInteger(0)

    case "SCARD":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'scard' command")
        }
        return protocol.NewInteger(store.SCard(args[0]))

    case "SINTER":
        return toArrayResponse(store.SInter(args...))

    case "SUNION":
        return toArrayResponse(store.SUnion(args...))

    // Key commands
    case "KEYS":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'keys' command")
        }
        return toArrayResponse(store.Keys(args[0]))

    case "TTL":
        if len(args) < 1 {
            return protocol.NewError("ERR wrong number of arguments for 'ttl' command")
        }
        return protocol.NewInteger(store.TTL(args[0]))

    default:
        return protocol.NewError("ERR unknown command '" + cmd + "'")
    }
}

func parseInt(s string) int64 {
    var n int64
    fmt.Sscanf(s, "%d", &n)
    return n
}

func toArrayResponse(values []string) protocol.Value {
    if values == nil {
        return protocol.NewArray(nil)
    }
    result := make([]protocol.Value, len(values))
    for i, v := range values {
        result[i] = protocol.NewBulkString(v)
    }
    return protocol.NewArray(result)
}
```

```go cmd/server/main.go theme={null}
package main

import (
    "flag"
    "log"
    "os"
    "os/signal"
    "syscall"

    "myredis/internal/server"
)

func main() {
    port := flag.String("port", "6379", "Port to listen on")
    flag.Parse()

    addr := ":" + *port

    srv, err := server.New(addr)
    if err != nil {
        log.Fatalf("Failed to create server: %v", err)
    }

    // Handle graceful shutdown
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        <-sigCh
        log.Println("Shutting down...")
        srv.Stop()
    }()

    log.Printf("Starting MyRedis server on %s", addr)
    if err := srv.Start(); err != nil {
        log.Fatalf("Server error: %v", err)
    }
}
```

***

## Testing Your Redis

```bash theme={null}
# Build and run
go build -o myredis ./cmd/server
./myredis -port 6379

# In another terminal, use redis-cli
redis-cli
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> SET hello world
OK
127.0.0.1:6379> GET hello
"world"
127.0.0.1:6379> LPUSH mylist a b c
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "c"
2) "b"
3) "a"
```

***

## Advanced Topics

### 1. Persistence (RDB Snapshots)

```go theme={null}
// internal/persistence/rdb.go
package persistence

import (
    "encoding/gob"
    "os"
    "myredis/internal/store"
)

func SaveRDB(store *store.Store, filename string) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    encoder := gob.NewEncoder(file)
    // Serialize store data...
    return nil
}
```

### 2. Pub/Sub

```go theme={null}
// internal/pubsub/pubsub.go
package pubsub

import (
    "sync"
)

type PubSub struct {
    channels map[string]map[chan string]struct{}
    mu       sync.RWMutex
}

func (ps *PubSub) Subscribe(channel string) chan string {
    ps.mu.Lock()
    defer ps.mu.Unlock()

    if ps.channels[channel] == nil {
        ps.channels[channel] = make(map[chan string]struct{})
    }

    ch := make(chan string, 100)
    ps.channels[channel][ch] = struct{}{}
    return ch
}

func (ps *PubSub) Publish(channel, message string) int {
    ps.mu.RLock()
    defer ps.mu.RUnlock()

    subscribers := ps.channels[channel]
    for ch := range subscribers {
        select {
        case ch <- message:
        default:
            // Channel full, skip
        }
    }
    return len(subscribers)
}
```

***

## Exercises

### Level 1: Core Implementation

1. Add APPEND command
2. Implement SETEX (set with expiry)
3. Add LINDEX command

### Level 2: Advanced Features

1. Implement Sorted Sets with skip list
2. Add RDB persistence
3. Implement MULTI/EXEC transactions

### Level 3: Production Features

1. Add AOF persistence
2. Implement cluster mode
3. Add Lua scripting support

***

## What You've Learned

> [!TIP]
>
> TCP server implementation in Go

> [!TIP]
>
> Binary protocol parsing (RESP)

> [!TIP]
>
> Concurrent data structure design

> [!TIP]
>
> Memory management and expiration

> [!TIP]
>
> Production-grade Go patterns

***

## Next Steps

> **[Java Implementation](https://resources.devweekends.com/courses/build-your-own-x/redis-java)**
>
> See how concurrency differs in Java

> **[JavaScript Implementation](https://resources.devweekends.com/courses/build-your-own-x/redis-js)**
>
> Node.js event loop approach

> **[Build Docker](https://resources.devweekends.com/courses/build-your-own-x/docker-overview.md)**
>
> Ready for the ultimate challenge?


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.