Lazy initialization
MSDN
C#
Lazy\`\`\`\``<T>`
\`\`\`\` class
.NET programming

Purpose of LazyT on this MSDN Example

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Lazy<T> in .NET delays object creation until the value is actually needed. The main purpose is controlling expensive initialization and avoiding startup cost for dependencies that may never be used. It can also help with thread-safe one-time initialization when configured correctly.

Core Sections

What Lazy Initialization Solves

Without lazy initialization, constructors often build dependencies eagerly, even when code paths never use them.

csharp
1public class ReportService
2{
3    private readonly ExpensiveClient _client = new ExpensiveClient();
4
5    public string RunReport()
6    {
7        return "report";
8    }
9}

If RunReport does not always need the client, startup cost is wasted.

Basic Lazy Usage

Wrap expensive dependencies with Lazy<T> so creation occurs on first access.

csharp
1using System;
2
3public class ReportService
4{
5    private readonly Lazy<ExpensiveClient> _client =
6        new Lazy<ExpensiveClient>(() => new ExpensiveClient());
7
8    public string FetchRemoteData()
9    {
10        return _client.Value.GetData();
11    }
12}
13
14public class ExpensiveClient
15{
16    public ExpensiveClient()
17    {
18        Console.WriteLine("Client created");
19    }
20
21    public string GetData() => "ok";
22}

Object creation now happens only when Value is accessed.

Thread Safety Modes

Lazy<T> supports different thread-safety behaviors. Default mode is thread-safe in most scenarios, but explicit modes can clarify intent.

csharp
1var lazyA = new Lazy<ExpensiveClient>(
2    () => new ExpensiveClient(),
3    isThreadSafe: true
4);

For high-throughput systems, pick mode based on contention characteristics and initialization semantics.

Exception Behavior and Retries

If the factory throws, Lazy<T> can cache exceptions depending on mode and usage. This surprises teams expecting retries on later access. If retry-on-failure is needed, wrap factory logic with custom policies.

csharp
var lazyConfig = new Lazy<string>(() => LoadConfigFromRemote());

Be explicit about failure handling in production services.

Avoid Misusing Lazy in Simple Cases

Not every dependency should be lazy. If a dependency is always required, eager initialization is simpler and often clearer. Overusing Lazy<T> can hide dependency graphs and complicate tests.

DI Container Integration

Many dependency injection containers already support lazy resolution. Prefer container idioms when available so lifecycle and disposal remain consistent.

csharp
1public class Handler
2{
3    private readonly Lazy<ExpensiveClient> _client;
4
5    public Handler(Lazy<ExpensiveClient> client)
6    {
7        _client = client;
8    }
9}

This keeps construction policies centralized.

Testing Lazy Behavior

Unit tests should verify that initialization happens exactly once and only when needed. This ensures laziness is intentional, not accidental.

Lazy and Caching Patterns in Real Services

In real services, Lazy<T> is often paired with cached lookups or singleton-like expensive providers. This can be effective when object creation is expensive and usage frequency is uncertain.

csharp
1public sealed class ConfigProvider
2{
3    private readonly Lazy<Dictionary<string, string>> _cache =
4        new Lazy<Dictionary<string, string>>(() => LoadConfig());
5
6    public string? Get(string key)
7    {
8        var data = _cache.Value;
9        return data.TryGetValue(key, out var v) ? v : null;
10    }
11
12    private static Dictionary<string, string> LoadConfig()
13    {
14        return new Dictionary<string, string>
15        {
16            ["Region"] = "us-east-1",
17            ["Mode"] = "prod"
18        };
19    }
20}

This keeps startup fast while still enabling quick repeated reads after first access.

Observability Around Lazy Initialization

When lazy initialization hides expensive work, add timing logs around first access to avoid hidden latency surprises. First-hit latency can impact user-facing requests if initialization happens on critical paths. Recording initialization duration helps decide whether eager warmup is better for some deployments.

Operationally, some teams trigger lazy objects during health checks in warmup phases so user traffic does not pay the first-hit cost.

Common Pitfalls

  • Using Lazy<T> where eager construction would be simpler and clearer.
  • Assuming exceptions always retry automatically on next access.
  • Ignoring thread-safety mode in concurrent code paths.
  • Hiding critical dependencies behind lazy wrappers and reducing readability.
  • Forgetting disposal concerns when lazy-created resources hold unmanaged handles.

Summary

  • Lazy<T> defers expensive object creation until first use.
  • It helps reduce startup cost and support one-time initialization.
  • Thread-safety and exception behavior should be chosen deliberately.
  • Use lazy loading where demand is conditional, not universal.
  • Validate lazy behavior with focused tests and clear design intent.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.