DevPrep
  • Interview Prep
  • Projects
  • Resources
  • Pricing
  • About Us
Submit Question
DevPrep
  • Pricing
  • About Us
Submit Question

Practice

  • JavaScript
  • DSA
  • Machine Coding
  • System Design

Resources

  • Learning Tracks
  • Articles
  • Roadmaps
  • Compare Concepts
  • Glossary
  • Developer Tools
  • All Questions

Company

  • About
  • Pricing

Legal

  • Privacy Policy
  • Terms of Service
DevPrep

© 2026 DevPrep. All rights reserved.

← Back to Questions
HardMachine Coding

Implement Consistent Hashing

81 views

Problem Statement

Implement consistent hashing for distributed cache/database sharding. Adding or removing a node should only affect K/N keys on average.

Requirements

  • AddNode(id): Add a node to the ring
  • RemoveNode(id): Remove a node from the ring
  • GetNode(key): Find the node responsible for a key
  • Virtual nodes for better distribution

Implementation

package consistenthash

import (
    "hash/crc32"
    "sort"
    "strconv"
    "sync"
)

type Ring struct {
    mu           sync.RWMutex
    nodes        map[uint32]string // hash -> node ID
    sortedHashes []uint32
    vnodes       int // virtual nodes per physical node
}

func New(vnodes int) *Ring {
    if vnodes < 1 {
        vnodes = 100 // default
    }
    return &Ring{
        nodes:  make(map[uint32]string),
        vnodes: vnodes,
    }
}

func (r *Ring) hash(key string) uint32 {
    return crc32.ChecksumIEEE([]byte(key))
}

func (r *Ring) AddNode(nodeID string) {
    r.mu.Lock()
    defer r.mu.Unlock()
    
    for i := 0; i < r.vnodes; i++ {
        vkey := nodeID + "#" + strconv.Itoa(i)
        h := r.hash(vkey)
        r.nodes[h] = nodeID
        r.sortedHashes = append(r.sortedHashes, h)
    }
    
    sort.Slice(r.sortedHashes, func(i, j int) bool {
        return r.sortedHashes[i] < r.sortedHashes[j]
    })
}

func (r *Ring) RemoveNode(nodeID string) {
    r.mu.Lock()
    defer r.mu.Unlock()
    
    for i := 0; i < r.vnodes; i++ {
        vkey := nodeID + "#" + strconv.Itoa(i)
        h := r.hash(vkey)
        delete(r.nodes, h)
    }
    
    // Rebuild sorted hashes
    r.sortedHashes = r.sortedHashes[:0]
    for h := range r.nodes {
        r.sortedHashes = append(r.sortedHashes, h)
    }
    sort.Slice(r.sortedHashes, func(i, j int) bool {
        return r.sortedHashes[i] < r.sortedHashes[j]
    })
}

func (r *Ring) GetNode(key string) string {
    r.mu.RLock()
    defer r.mu.RUnlock()
    
    if len(r.sortedHashes) == 0 {
        return ""
    }
    
    h := r.hash(key)
    
    // Binary search for first node >= hash
    idx := sort.Search(len(r.sortedHashes), func(i int) bool {
        return r.sortedHashes[i] >= h
    })
    
    // Wrap around to first node
    if idx >= len(r.sortedHashes) {
        idx = 0
    }
    
    return r.nodes[r.sortedHashes[idx]]
}

func (r *Ring) GetNodes(key string, n int) []string {
    r.mu.RLock()
    defer r.mu.RUnlock()
    
    if len(r.sortedHashes) == 0 {
        return nil
    }
    
    h := r.hash(key)
    idx := sort.Search(len(r.sortedHashes), func(i int) bool {
        return r.sortedHashes[i] >= h
    })
    
    seen := make(map[string]bool)
    result := make([]string, 0, n)
    
    for i := 0; i < len(r.sortedHashes) && len(result) < n; i++ {
        nodeIdx := (idx + i) % len(r.sortedHashes)
        nodeID := r.nodes[r.sortedHashes[nodeIdx]]
        
        if !seen[nodeID] {
            seen[nodeID] = true
            result = append(result, nodeID)
        }
    }
    
    return result
}

Usage

func main() {
    ring := consistenthash.New(100)
    
    ring.AddNode("server-1")
    ring.AddNode("server-2")
    ring.AddNode("server-3")
    
    // Keys consistently map to same nodes
    fmt.Println(ring.GetNode("user:123")) // server-2
    fmt.Println(ring.GetNode("user:456")) // server-1
    
    // Adding a node only affects nearby keys
    ring.AddNode("server-4")
    
    // Get replicas
    replicas := ring.GetNodes("user:123", 3)
    fmt.Println(replicas) // [server-2 server-3 server-1]
}

Sample Test Cases

Case 1
Input
nodes=[A,B,C], key="user123"
Expected Output
Returns consistent node for same key
Case 2
Input
[
    ["AddNode", "server-1"],
    ["AddNode", "server-2"],
    ["AddNode", "server-3"],
    ["GetNode", "user:1"],
    ["GetNode", "user:2"],
    ["GetNode", "user:3"]
]
Expected Output
["server-1", "server-2", "server-3"]
Case 3
Input
nodes=[A,B,C], remove=B, key="user123"
Expected Output
Minimal key redistribution when node removed
Case 4
Input
[
    ["AddNode", "server-A"],
    ["AddNode", "server-B"],
    ["RemoveNode", "server-A"],
    ["GetNode", "key-X"],
    ["GetNode", "key-Y"]
]
Expected Output
["server-B", "server-B"]
Case 5
Input
[
    ["AddNode", "node-1"],
    ["AddNode", "node-2"],
    ["AddNode", "node-3"],
    ["GetNodes", "item-1", 2],
    ["GetNodes", "item-2", 3]
]
Expected Output
[["node-1", "node-2"], ["node-3", "node-1", "node-2"]]

No solutions yet

Be the first to share a solution for this question.

Comments (0)

Sign in to leave a comment.

No comments yet. Be the first to comment.

Stats

Views
81
Likes
0
Solutions
0
Comments
0

Category

Backend Engineering

Languages

Go