> ## 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

<img src="https://mintcdn.com/devweeekends/AEOaWh79Ur7CdHHv/images/courses/build-your-own-x/redis-resp.svg?fit=max&auto=format&n=AEOaWh79Ur7CdHHv&q=85&s=377d9f4a70acb77690c8ff03d6b53e6f" alt="RESP Protocol" width="1080" height="1080" data-path="images/courses/build-your-own-x/redis-resp.svg" />

# 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.
</Warning>

***

## 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

<CodeGroup>
  ```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)
      }
  }
  ```
</CodeGroup>

***

## 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

<Check>TCP server implementation in Go</Check>
<Check>Binary protocol parsing (RESP)</Check>
<Check>Concurrent data structure design</Check>
<Check>Memory management and expiration</Check>
<Check>Production-grade Go patterns</Check>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Java Implementation" icon="java" href="/courses/build-your-own-x/redis-java">
    See how concurrency differs in Java
  </Card>

  <Card title="JavaScript Implementation" icon="js" href="/courses/build-your-own-x/redis-js">
    Node.js event loop approach
  </Card>

  <Card title="Build Docker" icon="docker" href="/courses/build-your-own-x/docker-overview">
    Ready for the ultimate challenge?
  </Card>
</CardGroup>
