Lazy Initialization
Lazy Initialization is a design pattern where an object is not created until it is actually needed. Instead of allocating memory and doing work upfront, you delay initialization until the first time the value is accessed.
Lazy initialization (lazy instantiation) of an object means that its creation is deferred until it is first used. (For this topic, the terms lazy initialization and lazy instantiation are synonymous.) Lazy initialization is primarily used to improve performance, avoid wasteful computation, and reduce program memory requirements.
What Lazy Initialization means?
Normally:
• You create everything at startup
With lazy initialization:
• You create something only when it’s first used
Think of it like:
• “Don’t prepare the meal until the customer actually orders it.”
Why use Lazy Initialization?
1. Improve performance
Avoid expensive work during startup.
2. Save memory
Objects are created only if needed.
3. Reduce unnecessary computation
Some objects may never be used at all.
4. Improve application startup time
Especially useful in large systems.
When should you use it?
Lazy initialization is useful when:
• Object creation is expensive (DB calls, file I/O, heavy computation)
• Object may not always be used
• You want to delay initialization until needed
• You want thread-safe lazy loading
Not ideal when:
• Object is always used immediately anyway
• Initialization is trivial (no benefit)
• You need predictable startup behavior
Basic example (without Lazy)
public class Service
{
private HeavyObject obj = new HeavyObject();
public HeavyObject GetObject()
{
return obj;
}
}
Problem: HeavyObject is created even if never used
Lazy Initialization with Lazy<T>
public class Service
{
private Lazy<HeavyObject> obj = new Lazy<HeavyObject>(() => new HeavyObject());
public HeavyObject GetObject()
{
return obj.Value;
}
}
Now: HeavyObject is created only when Value is accessed
Thread-safe lazy initialization
By default, Lazy<T> is thread-safe:
Lazy<HeavyObject> obj = new Lazy<HeavyObject>(() => new HeavyObject(), true);
This ensures: Only one instance is created even in multi-threaded environments
Key features of Lazy<T>
• Delayed creation
• Thread safety (optional modes)
• Exception caching (if initialization fails, it rethrows)
• Simple syntax
Common use cases
1. Database connections
Avoid opening connection until needed.
2. Configuration loading
Load config only when accessed.
3. Heavy services
Image processors, ML models, etc.
4. Singleton pattern
Often combined with lazy initialization:
public static Lazy<MyService> Instance =
new Lazy<MyService>(() => new MyService());
Advantages
• Faster application startup
• Lower memory usage
• Efficient resource management
• Built-in thread safety
• Cleaner code than manual checks
Disadvantages
• Slight overhead on first access
• Can hide initialization cost (delayed errors)
• May complicate debugging
• Not useful if object is always needed
Most common scenarios of Lazy Initialization
• When you have an object that is expensive to create, and the program might not use it. For example, assume that you have in memory a Customer object that has an Orders property that contains a large array of Order objects that, to be initialized, requires a database connection. If the user never asks to display the Orders or use the data in a computation, then there is no reason to use system memory or computing cycles to create it. By using Lazy to declare the Orders object for lazy initialization, you can avoid wasting system resources when the object is not used.
• When you have an object that is expensive to create, and you want to defer its creation until after other expensive operations have been completed. For example, assume that your program loads several object instances when it starts, but only some of them are required immediately. You can improve the startup performance of the program by deferring initialization of the objects that are not required until the required objects have been created.
Although you can write your own code to perform lazy initialization, we recommend that you use Lazy instead. Lazy and its related types also support thread-safety and provide a consistent exception propagation policy.