# Lazy Loading Pattern

## Problem

When loading an entity from the database, eagerly loading all its related entities creates excessive queries and memory usage — especially in deep object graphs. A `User` with `orders`, each with `line_items`, each with `product` can trigger dozens of queries before any data is actually needed.

## Solution

Defer loading of related data until it's actually accessed. The entity holds a placeholder (proxy, thunk, or descriptor) instead of the real data. On first access, the placeholder triggers the actual load. Subsequent accesses return the cached result.

Common variants:
- **Virtual proxy** — A proxy object that loads the real object on first method call.
- **Value holder** — A wrapper that holds a loader function and caches the result.
- **Ghost** — A partially loaded object that fills itself in on first access.

## When to Use

- Object graphs with optional or rarely-accessed relations
- APIs where clients request partial data
- Reducing initial query cost when not all fields are needed
- ORM implementations (virtually all ORMs use lazy loading)

## When to Avoid

- When you know you'll need the related data (eager load instead)
- In serialization contexts (lazy loads during JSON encoding cause surprises)
- When N+1 query problems are likely (batch loading is better)
- Real-time systems where deferred I/O introduces unpredictable latency

## Pseudocode

```
class Lazy:
    loaded = false
    value = null
    loader = function

    function get():
        if not loaded:
            value = loader()
            loaded = true
        return value
```

## Python Implementation

```python
from typing import TypeVar, Generic, Callable, Optional

T = TypeVar("T")


class Lazy(Generic[T]):
    """Value holder that defers loading until first access."""

    def __init__(self, loader: Callable[[], T]):
        self._loader = loader
        self._value: Optional[T] = None
        self._loaded = False

    @property
    def value(self) -> T:
        if not self._loaded:
            self._value = self._loader()
            self._loaded = True
        return self._value  # type: ignore

    @property
    def is_loaded(self) -> bool:
        return self._loaded


class User:
    def __init__(self, user_id: int, name: str, order_loader: Callable):
        self.id = user_id
        self.name = name
        self._orders = Lazy(order_loader)

    @property
    def orders(self):
        return self._orders.value

    @property
    def orders_loaded(self):
        return self._orders.is_loaded


class Order:
    def __init__(self, order_id: int, total: float):
        self.id = order_id
        self.total = total

    def __repr__(self):
        return f"Order(id={self.id}, total=${self.total:.2f})"


# --- Simulated database ---
DB_ORDERS = {
    1: [Order(101, 29.99), Order(102, 49.50)],
    2: [Order(201, 15.00)],
}

query_count = 0

def load_orders_for(user_id: int):
    global query_count
    query_count += 1
    print(f"  [DB] Loading orders for user {user_id} (query #{query_count})")
    return DB_ORDERS.get(user_id, [])


# --- Demo ---
print("=== Creating user (orders NOT loaded yet) ===")
user = User(1, "Alice", lambda: load_orders_for(1))
print(f"  User: {user.name}, orders_loaded={user.orders_loaded}")

print()
print("=== First access to orders (triggers load) ===")
orders = user.orders
print(f"  Orders: {orders}")
print(f"  orders_loaded={user.orders_loaded}")

print()
print("=== Second access (cached, no query) ===")
orders_again = user.orders
print(f"  Orders: {orders_again}")
print(f"  Same list? {orders is orders_again}")

print()
print("=== User 2 — never access orders ===")
user2 = User(2, "Bob", lambda: load_orders_for(2))
print(f"  User: {user2.name}, orders_loaded={user2.orders_loaded}")

print()
print(f"Total DB queries: {query_count} (would be 2 with eager loading)")
```

**Output:**
```
=== Creating user (orders NOT loaded yet) ===
  User: Alice, orders_loaded=False

=== First access to orders (triggers load) ===
  [DB] Loading orders for user 1 (query #1)
  Orders: [Order(id=101, total=$29.99), Order(id=102, total=$49.50)]
  orders_loaded=True

=== Second access (cached, no query) ===
  Orders: [Order(id=101, total=$29.99), Order(id=102, total=$49.50)]
  Same list? True

=== User 2 — never access orders ===
  User: Bob, orders_loaded=False

Total DB queries: 1 (would be 2 with eager loading)
```

## Go Implementation

```go
package main

import (
	"fmt"
	"sync"
)

type Lazy[T any] struct {
	loader func() T
	value  T
	loaded bool
	once   sync.Once
}

func NewLazy[T any](loader func() T) *Lazy[T] {
	return &Lazy[T]{loader: loader}
}

func (l *Lazy[T]) Get() T {
	l.once.Do(func() {
		l.value = l.loader()
		l.loaded = true
	})
	return l.value
}

func (l *Lazy[T]) IsLoaded() bool {
	return l.loaded
}

// Domain types
type Order struct {
	ID    int
	Total float64
}

func (o Order) String() string {
	return fmt.Sprintf("Order(id=%d, total=$%.2f)", o.ID, o.Total)
}

type User struct {
	ID     int
	Name   string
	orders *Lazy[[]Order]
}

func (u *User) Orders() []Order {
	return u.orders.Get()
}

// Simulated DB
var queryCount int

func loadOrders(userID int) []Order {
	queryCount++
	fmt.Printf("  [DB] Loading orders for user %d (query #%d)\n", userID, queryCount)
	db := map[int][]Order{
		1: {{101, 29.99}, {102, 49.50}},
		2: {{201, 15.00}},
	}
	return db[userID]
}

func main() {
	fmt.Println("=== Creating user (orders NOT loaded yet) ===")
	user := &User{
		ID:     1,
		Name:   "Alice",
		orders: NewLazy(func() []Order { return loadOrders(1) }),
	}
	fmt.Printf("  User: %s, orders_loaded=%v\n", user.Name, user.orders.IsLoaded())

	fmt.Println()
	fmt.Println("=== First access (triggers load) ===")
	orders := user.Orders()
	fmt.Printf("  Orders: %v\n", orders)

	fmt.Println()
	fmt.Println("=== Second access (cached) ===")
	orders2 := user.Orders()
	fmt.Printf("  Orders: %v\n", orders2)

	fmt.Println()
	fmt.Println("=== User 2 — never access orders ===")
	user2 := &User{
		ID:     2,
		Name:   "Bob",
		orders: NewLazy(func() []Order { return loadOrders(2) }),
	}
	fmt.Printf("  User: %s, orders_loaded=%v\n", user2.Name, user2.orders.IsLoaded())

	fmt.Printf("\nTotal DB queries: %d\n", queryCount)
}
```

## JavaScript Implementation

```javascript
class Lazy {
  constructor(loader) {
    this._loader = loader;
    this._value = undefined;
    this._loaded = false;
  }

  get value() {
    if (!this._loaded) {
      this._value = this._loader();
      this._loaded = true;
    }
    return this._value;
  }

  get isLoaded() {
    return this._loaded;
  }
}

// Domain
class User {
  constructor(id, name, orderLoader) {
    this.id = id;
    this.name = name;
    this._orders = new Lazy(orderLoader);
  }

  get orders() {
    return this._orders.value;
  }

  get ordersLoaded() {
    return this._orders.isLoaded;
  }
}

// Simulated DB
let queryCount = 0;
const DB_ORDERS = {
  1: [
    { id: 101, total: 29.99 },
    { id: 102, total: 49.5 },
  ],
  2: [{ id: 201, total: 15.0 }],
};

function loadOrders(userId) {
  queryCount++;
  console.log(`  [DB] Loading orders for user ${userId} (query #${queryCount})`);
  return DB_ORDERS[userId] || [];
}

// --- Demo ---
console.log("=== Creating user (orders NOT loaded) ===");
const user = new User(1, "Alice", () => loadOrders(1));
console.log(`  User: ${user.name}, ordersLoaded=${user.ordersLoaded}`);

console.log("\n=== First access (triggers load) ===");
console.log(`  Orders: ${JSON.stringify(user.orders)}`);

console.log("\n=== Second access (cached) ===");
console.log(`  Orders: ${JSON.stringify(user.orders)}`);
console.log(`  ordersLoaded=${user.ordersLoaded}`);

console.log("\n=== User 2 — never access orders ===");
const user2 = new User(2, "Bob", () => loadOrders(2));
console.log(`  User: ${user2.name}, ordersLoaded=${user2.ordersLoaded}`);

console.log(`\nTotal DB queries: ${queryCount}`);
```

## Related Patterns

- **Proxy** — a virtual proxy is the most common implementation of lazy loading; the proxy loads the real object on first access.
- **Identity Map** — lazy loading must check the identity map before issuing a query to avoid loading the same entity twice.
- **Repository** — repositories trigger lazy loading when related collections are first accessed.
