Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Core Key-Value Store Operations

Hello! Welcome to the first lesson in our new module, "Building Systems Incrementally."

In the last module, we honed your skills in analyzing advanced algorithmic patterns, culminating in a deep dive into the performance trade-offs between heap-based solutions, sorting, and QuickSelect. That kind of analytical thinking is crucial for making sound engineering decisions. Now, we'll apply that mindset as we shift from standalone algorithms to building small, practical systems—a format that closely mirrors the interview style at companies like Anthropic.

Today, we're starting with a foundational component of many distributed systems: an in-memory key-value store. You'll implement the three core operations—GET, SET, and DELETE. This task, while seemingly simple, is a classic interview starting point that tests your grasp of basic data structures and clean API design before layering on more complexity.

By the end of this lesson, you will be able to implement the core GET, SET, and DELETE operations for a basic in-memory key-value store in Python.


1. The Key-Value Model: A Universal Primitive

At its core, a key-value store is one of the simplest data models. It functions like a dictionary where you store some data (the "value") and associate it with a unique identifier (the "key"). This pattern is ubiquitous in software engineering, forming the backbone of caches (like Redis), configuration services, and even parts of the complex infrastructure you've built at AWS.

The interface is defined by three fundamental operations:

  • SET(key, value): Inserts a new key-value pair or updates the value if the key already exists.
  • GET(key): Retrieves the value associated with a given key.
  • DELETE(key): Removes a key-value pair from the store.

To see these operations in action, let's watch a very brief segment from a talk on building a high-performance key-value store.

GopherCon 2017: Building a High-Performance Key/Value Store in Go - Marty Schoch

This clip from a GopherCon talk provides a crisp, clear definition of a key-value store and its fundamental API: GET, SET, and DELETE.

Watch the segment from 00:53 to 01:38. It perfectly illustrates the three core operations we'll be implementing.

2. The Engine Room: Choosing the Right Data Structure

For an in-memory store, our primary goal is speed. We need a data structure that provides near-instantaneous lookups, insertions, and deletions. The ideal candidate for this is the hash map.

As a Rust developer, you're already very familiar with std::collections::HashMap. Python's built-in dict type is its direct equivalent and is also implemented as a hash map, giving us the performance characteristics we need.

To achieve its impressive average-case time complexity, a hash map uses a hashing function to convert a key into an index in an underlying array. Of course, sometimes different keys can hash to the same index, an event known as a collision. The most common strategy to resolve this is chaining, where a linked list (or similar structure) is used at each array index to store all the key-value pairs that collide.

Let's watch a short video that explains these core mechanics. This will be a valuable refresher, as we'll be implementing a hash map from scratch in the next module.

Design Hashmap - Leetcode 706 - Python

This NeetCode video gives a great visual explanation of how a hash map uses a hashing function and resolves collisions with chaining.

Please watch from 01:17 to 05:13. Focus on understanding how the modulo operator is used to create an index and how chaining with linked lists handles collisions. You don't need to worry about the specific code for now, just the concept.

The key takeaway is that by using a hash map (dict in our case), we can expect our GET, SET, and DELETE operations to perform in constant time on average.


3. From Theory to Production-Quality Python

Now, let's translate this into a Python class. We will create a KeyValueStore that encapsulates a dictionary for storage and exposes our three main methods.

Here's a skeleton of the class you will complete:

class KeyValueStore:
    """
    An in-memory key-value store.
    """
    def __init__(self):
        """
        Initializes the key-value store.
        """
        self._data = {}

    def set(self, key: str, value: any):
        """
        Sets the value for a given key. Overwrites the existing value if the key is already present.
        
        Args:
            key: The key to set.
            value: The value to associate with the key.
        """



        # Your implementation here
        pass

    def get(self, key: str) -> any:
        """
        Gets the value for a given key.
        
        Args:
            key: The key to retrieve.
            
        Returns:
            The value associated with the key, or None if the key is not found.
        """



        # Your implementation here
        pass

    def delete(self, key: str) -> bool:
        """
        Deletes a key-value pair.
        
        Args:
            key: The key to delete.
            
        Returns:
            True if the key was found and deleted, False otherwise.
        """



        # Your implementation here
        pass

Your Task: Implement the set, get, and delete methods.

Here are some guidelines based on Python best practices:

  • set(key, value): This is the most straightforward. You can simply assign the value to the key in the _data dictionary.
  • get(key): A key might not exist. A robust implementation should not crash. Instead of using self._data[key], which would raise a KeyError, it's better to use the dict.get() method. It allows you to safely retrieve a value and returns None (or a specified default) if the key is not found.
  • delete(key): Similar to get, attempting to delete a non-existent key with del self._data[key] will raise a KeyError. A clean way to handle this is to check for the key's existence first (if key in self._data) or use a try...except KeyError block. The method should return a boolean indicating if the deletion was successful.

This exercise is not just about functionality but also about writing clean, predictable code—a key aspect of production readiness.


4. A Glimpse of a Production System

Our KeyValueStore class is a great start, but it has two major limitations for a real-world service like the ones you build:

  1. It's not durable: If the process restarts, all data is lost.
  2. It's not thread-safe: If multiple threads try to write data simultaneously, the internal state can become corrupted.

In a production system, durability is often achieved by writing operations to a Write-Ahead Log (WAL) on disk before updating the in-memory hash map. Concurrency is managed with locking mechanisms.

This diagram illustrates that common architecture. Our implementation today constitutes the "In-Memory Hashmap" component.

Key-Value Store Write Operation with Write-Ahead Log (WAL)
This diagram shows how a key-value store combines a durable on-disk Write-Ahead Log with a fast in-memory hash map. Write operations are appended to the log first (1) before the in-memory map is updated (2).

As we move through this module, we will incrementally add features like thread-safety, moving closer to a production-ready component.


Conclusion

In this lesson, we laid the groundwork for our system-building module. You've implemented the essential API for an in-memory key-value store, a fundamental building block in modern software architecture.

Key Takeaways:

  • A key-value store provides a simple data model with three core operations: GET, SET, and DELETE.
  • For an in-memory implementation, the hash map (Python's dict) is the optimal data structure, offering average time complexity for its core operations.
  • Writing robust methods involves gracefully handling edge cases, such as requests for non-existent keys.
  • A simple in-memory store is the first step. Production systems build on this with features for durability (like WALs) and concurrency.

Next Lesson Preview

Now that we have a working key-value store, we'll add our first feature. In the next lesson, you will extend the key-value store to support Time-To-Live (TTL), a common requirement for caches, where keys automatically expire after a set duration. This will require us to think about how we manage and evict stale data efficiently.

Can't find a good explanation? Sign up and we'll make it for you

Sign up