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.
Python code
43 linesclass 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
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
- Add a `find_by_email` method that scans values for a match
- 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
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.