Lazy loading with a proxy in Python: defer expensive service creation

A lazy proxy defers creating an expensive service object until its method is first called, then caches it for reuse.

Medium Python 3.9+ Aug 9, 2026 System design patterns 15 views 0 copies

Python code

37 lines
Python 3.9+
import time
import random


class ExpensiveService:
    def __init__(self, name):
        self.name = name
        print(f"Creating expensive service: {self.name}")

    def fetch_data(self):
        time.sleep(1)
        return f"Data from {self.name}: {random.randint(1, 100)}"


class LazyProxy:
    def __init__(self, name):
        self._name = name
        self._service = None

    def get_service(self):
        if self._service is None:
            self._service = ExpensiveService(self._name)
        return self._service

    def fetch_data(self):
        return self.get_service().fetch_data()


if __name__ == "__main__":
    print("Client starts...")
    proxy = LazyProxy("mock-service")
    print("Proxy created (expensive service NOT yet instantiated)")

    for i in range(3):
        print(f"Call {i + 1}: {proxy.fetch_data()}")

    print("Client ends...")

Output

stdout
Client starts...
Proxy created (expensive service NOT yet instantiated)
Creating expensive service: mock-service
Call 1: Data from mock-service: 42
Call 2: Data from mock-service: 37
Call 3: Data from mock-service: 89
Client ends...

How it works

The LazyProxy holds a reference to the real service name but delays constructing the ExpensiveService until fetch_data() is called. Inside get_service(), the proxy checks _service is None and only then creates the instance, printing the creation message just once. After the first call, _service is cached, so subsequent calls reuse the same object — notice the creation message appears only once even though fetch_data() is called three times. This mirrors the Proxy pattern where the proxy controls access to the real object, adding deferred initialization without changing the client interface.

Common mistakes

  • Instantiating the service in the proxy's __init__ instead of delaying until first use.
  • Forgetting to cache the service, causing a new instance on every call.
  • Not handling thread safety when lazy initialization is shared across threads.

Variations

  1. Use a property on the proxy to lazily initialize with an internal `_get_service` method.

Real-world use cases

  • Deferring the creation of heavy database connections until the first query.
  • Mocking external API clients in tests to avoid network calls until a method is actually used.
  • Optimizing startup time in services by lazily loading expensive resources like ML models.

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.