How to Implement the Repository Pattern in Python with an In-Memory Dict

Stores, retrieves, updates, and deletes user records in memory using a Repository abstraction over a plain dict, isolating data access from business logic.

Easy Python 3.9+ Aug 9, 2026 System design patterns 11 views 0 copies

Python code

43 lines
Python 3.9+
class UserRepository:
    def __init__(self):
        self._storage = {}
        self._next_id = 1

    def create(self, name, email):
        user_id = self._next_id
        self._next_id += 1
        self._storage[user_id] = {"id": user_id, "name": name, "email": email}
        return self._storage[user_id]

    def get(self, user_id):
        return self._storage.get(user_id)

    def get_all(self):
        return list(self._storage.values())

    def update(self, user_id, name=None, email=None):
        if user_id not in self._storage:
            return None
        user = self._storage[user_id]
        if name is not None:
            user["name"] = name
        if email is not None:
            user["email"] = email
        return user

    def delete(self, user_id):
        return self._storage.pop(user_id, None)


if __name__ == "__main__":
    repo = UserRepository()
    repo.create("Alice", "alice@example.com")
    repo.create("Bob", "bob@example.com")

    updated = repo.update(1, name="Alice Smith")
    deleted = repo.delete(2)

    print("All users:", repo.get_all())
    print("User 1:", repo.get(1))
    print("User 2 after delete:", repo.get(2))
    print("Deleted user:", deleted)

Output

stdout
All users: [{'id': 1, 'name': 'Alice Smith', 'email': 'alice@example.com'}]
User 1: {'id': 1, 'name': 'Alice Smith', 'email': 'alice@example.com'}
User 2 after delete: None
Deleted user: {'id': 2, 'name': 'Bob', 'email': 'bob@example.com'}

How it works

Repository pattern decouples data access from the rest of the app. The _storage dict keys are auto-incrementing integer IDs, while the _next_id counter guarantees uniqueness. Each method returns None when a record is missing, so callers can check with a simple truth test. Using .get() and .pop() with defaults prevents KeyError crashes. This in-memory version is a template you swap with a database-backed repo later.

Common mistakes

  • Forgetting to increment `_next_id` after each create, causing duplicate IDs
  • Mutating the dict returned by `get` directly, breaking encapsulation
  • Not returning `None` from `update` when the ID is missing
  • Exposing `_storage` as public instead of keeping it underscore-prefixed

Variations

  1. Add a `find_by_email` method that scans values for a match
  2. Use dataclasses instead of dicts for richer record modeling

Real-world use cases

  • Seeding unit tests with fake repositories to avoid touching a real database.
  • Caching recently accessed records in a service layer for fast repeat reads.
  • Prototyping a service before wiring up SQLAlchemy or an external datastore.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.