100
creational performance

Lazy Initialization

Reference Wikipedia ↗
Lazy Initialization — class diagram
Plate 100 class diagram

Lazy Initialization is a technique that delays the creation of an object or the execution of a process until it is actually needed. Instead of initializing the object during the class or module loading phase, initialization is postponed to the first time the object’s methods are invoked or its properties are accessed. This can significantly improve application startup time and reduce resource consumption, especially when dealing with resource-intensive operations.

The pattern is particularly useful when you have objects that require significant resources to create, but aren’t always used by the application. Avoid unnecessary initialization costs by waiting until the object is explicitly requested. It’s also helpful when initialization depends on runtime information that isn’t available at startup.

Usage

  • Improving Startup Time: When an application has many dependencies, some of which are expensive to initialize, lazy initialization can drastically reduce the time it takes for the application to become responsive.
  • Resource Management: It’s beneficial when dealing with limited resources like database connections or file handles. Initializing them only when needed prevents resource exhaustion.
  • Conditional Initialization: If an object is only required under certain conditions, lazy initialization avoids initializing it if those conditions are never met.
  • Singleton Pattern Implementation: Lazy initialization is often used to create singletons to ensure the instance is created only when first accessed.

Examples

  1. Java’s java.lang.ClassLoader: The Java class loader doesn’t load and initialize classes immediately when the program starts. Instead, it loads classes “on demand”, only when they are first referenced during program execution. This is a form of lazy initialization that improves startup time, as only the required classes are loaded.

  2. Python’s property decorator: Python’s @property decorator allows you to define methods that behave like attributes. These methods can use lazy initialization to compute a value only when it is first requested. For example, calculating a complex statistical value only when the property is accessed for the first time.

python class DataProcessor: def init(self, data): self.data = data self._processed_data = None

   @property
   def processed_data(self):
       if self._processed_data is None:
           print("Processing data...") #Simulating an expensive operation
           self._processed_data = self._process()
       return self._processed_data

   def _process(self):
       # Actual data processing logic
       return [x * 2 for x in self.data]

processor = DataProcessor([1, 2, 3])

processed_data is not calculated yet

print(“Main program continues…”)

The processing happens only when processed_data is accessed

print(processor.processed_data)

Specimens

15 implementations
Specimen 100.01 Dart View specimen ↗

The Lazy Initialization pattern delays the creation of an expensive object until its first use. This improves performance, especially if the object is not always needed. In Dart, this is commonly achieved using the lazySet or similar techniques within a class. The example below demonstrates this with a potentially resource-intensive DatabaseConnection class. The connection is not established until connection is accessed for the first time. This fits Dart’s style by using getter-based access and concise syntax for initialization checks within the getter.

class DatabaseConnection {
  final String databaseUrl;

  DatabaseConnection(this.databaseUrl);

  // Simulate an expensive database connection process
  Future<String> establishConnection() async {
    print('Establishing database connection to $databaseUrl...');
    await Future.delayed(Duration(seconds: 2)); // Simulate delay
    print('Database connection established.');
    return 'Connected to $databaseUrl';
  }
}

class DataService {
  final String dbUrl;
  String? _connectionString;

  DataService(this.dbUrl);

  Future<String> get connection async {
    if (_connectionString == null) {
      final connection = DatabaseConnection(dbUrl);
      _connectionString = await connection.establishConnection();
    }
    return _connectionString!;
  }

  Future<void> fetchData() async {
    final conn = await connection;
    print('Fetching data using connection: $conn');
  }
}

void main() async {
  final service = DataService('mongodb://localhost:27017/mydatabase');

  // Connection is not established yet
  print('Service created, connection lazy-loaded.');
  
  // Connection established when accessed for the first time
  await service.fetchData();

  // Subsequent accesses use the existing connection
  await service.fetchData();
}