# Proxy Design Pattern: Lazy Loading Explained in a Simple Way

## Introduction

The Proxy Design Pattern is a structural design pattern that places an intermediate object between the client and the real object.

Instead of accessing the real object directly, the client communicates with a proxy.

```text
Client → Proxy → Real Object
```

The proxy controls when and how the real object is accessed.

It can be used for:

*   Lazy loading
    
*   Security and authorization
    
*   Caching
    
*   Logging
    
*   Remote communication
    
*   Access control
    

In this article, we will understand the Proxy Pattern using a simple image lazy-loading example.

* * *

## What does “Proxy” mean?

A proxy is a representative of another object.

Consider a real-life example involving a company CEO.

A visitor cannot enter the CEO’s office directly. The visitor must first speak with the receptionist.

```text
Visitor → Receptionist → CEO
```

The receptionist may check:

*   Does the visitor have an appointment?
    
*   Is the CEO available?
    
*   What is the purpose of the meeting?
    
*   Should the visitor be allowed inside?
    

Here:

| Real-world component | Proxy Pattern component |
| --- | --- |
| Visitor | Client |
| Receptionist | Proxy |
| CEO | Real object |
| Meeting request | Method call |

The receptionist does not perform the CEO’s actual work. The receptionist only controls access to the CEO.

That is the basic idea behind the Proxy Pattern.

* * *

# Understanding Lazy Loading

Lazy loading means:

> Do not create or load an expensive object until it is actually required.

Suppose we are building an image gallery containing 100 high-quality images.

Loading an image may involve:

*   Downloading it from a server
    
*   Using internet bandwidth
    
*   Allocating memory
    
*   Decoding image pixels
    
*   Rendering the image on the screen
    

If the user can see only the first few images, loading all 100 images immediately would waste resources.

Instead, we can initially store only basic information:

```text
File name: shoe.jpg
Image URL: https://server.com/shoe.jpg
Image ID: 101
```

The actual image can be downloaded when it becomes visible.

This is called lazy loading.

* * *

# Problem Without Proxy

Consider the following `RealImage` class:

```java
class RealImage {

    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromServer();
    }

    private void loadFromServer() {
        System.out.println(
                fileName + " is loading from the server"
        );
    }

    public void display() {
        System.out.println(
                fileName + " is displayed"
        );
    }
}
```

The constructor immediately loads the image:

```java
public RealImage(String fileName) {
    this.fileName = fileName;
    loadFromServer();
}
```

Now imagine creating three images:

```java
public class Main {

    public static void main(String[] args) {

        RealImage image1 =
                new RealImage("photo1.jpg");

        RealImage image2 =
                new RealImage("photo2.jpg");

        RealImage image3 =
                new RealImage("photo3.jpg");

        image2.display();
    }
}
```

Output:

```text
photo1.jpg is loading from the server
photo2.jpg is loading from the server
photo3.jpg is loading from the server
photo2.jpg is displayed
```

Only `photo2.jpg` was displayed, but all three images were loaded.

This creates several problems:

*   Unnecessary network requests
    
*   Extra memory consumption
    
*   Slower application startup
    
*   Wasted bandwidth
    
*   Poor user experience
    

We need a way to store the image information now but load the actual image later.

That is where the Proxy Pattern helps.

* * *

# Solution Using Proxy Pattern

We will place an `ImageProxy` between the client and the real image:

```text
Client → ImageProxy → RealImage
```

The proxy will initially store only the file name.

It will create the `RealImage` object only when the client calls `display()`.

* * *

# Step 1: Create a Common Interface

```java
interface Image {
    void display();
}
```

This interface acts as a common contract for both the real object and the proxy.

Both classes will provide the same method:

```java
display()
```

Because of this common interface, the client can use the proxy exactly like a real image.

* * *

# Step 2: Create the Real Object

```java
class RealImage implements Image {

    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromServer();
    }

    private void loadFromServer() {
        System.out.println(
                fileName + " is loading from the server"
        );
    }

    @Override
    public void display() {
        System.out.println(
                fileName + " is displayed"
        );
    }
}
```

`RealImage` is responsible for the actual work:

*   Loading the image
    
*   Storing the real image data
    
*   Displaying the image
    

It is called the **Real Subject**.

* * *

# Step 3: Create the Proxy

```java
class ImageProxy implements Image {

    private String fileName;
    private RealImage realImage;

    public ImageProxy(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {

        if (realImage == null) {
            realImage = new RealImage(fileName);
        }

        realImage.display();
    }
}
```

Initially, the proxy stores only:

```java
private String fileName;
```

The real object is not created immediately:

```java
private RealImage realImage;
```

Its initial value is:

```text
realImage = null
```

When `display()` is called, the proxy checks whether the real object already exists:

```java
if (realImage == null) {
    realImage = new RealImage(fileName);
}
```

If it does not exist, the proxy creates it.

Finally, the proxy delegates the real work:

```java
realImage.display();
```

Delegation means:

> The proxy receives the request but passes the actual work to the real object.

* * *

# Complete Java Code

```java
interface Image {
    void display();
}

class RealImage implements Image {

    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromServer();
    }

    private void loadFromServer() {
        System.out.println(
                fileName + " is loading from the server"
        );
    }

    @Override
    public void display() {
        System.out.println(
                fileName + " is displayed"
        );
    }
}

class ImageProxy implements Image {

    private String fileName;
    private RealImage realImage;

    public ImageProxy(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {

        if (realImage == null) {
            realImage = new RealImage(fileName);
        }

        realImage.display();
    }
}

public class Main {

    public static void main(String[] args) {

        Image image1 =
                new ImageProxy("photo1.jpg");

        Image image2 =
                new ImageProxy("photo2.jpg");

        Image image3 =
                new ImageProxy("photo3.jpg");

        System.out.println(
                "All proxy objects have been created"
        );

        image2.display();
    }
}
```

Output:

```text
All proxy objects have been created
photo2.jpg is loading from the server
photo2.jpg is displayed
```

Notice that `photo1.jpg` and `photo3.jpg` were not loaded because the client never displayed them.

* * *

# Step-by-Step Execution

## Step 1: Proxy objects are created

```java
Image image2 =
        new ImageProxy("photo2.jpg");
```

At this point, the memory state is:

```text
image2
   ↓
ImageProxy
   fileName = "photo2.jpg"
   realImage = null
```

The proxy contains only lightweight information.

No server request has been made yet.

* * *

## Step 2: The client calls `display()`

```java
image2.display();
```

The reference type is `Image`, but the actual object is `ImageProxy`.

Therefore, Java executes:

```java
ImageProxy.display()
```

* * *

## Step 3: The proxy checks the real object

```java
if (realImage == null)
```

The real image does not exist yet, so the condition is true.

* * *

## Step 4: The proxy creates the real image

```java
realImage = new RealImage(fileName);
```

The `RealImage` constructor executes:

```java
public RealImage(String fileName) {
    this.fileName = fileName;
    loadFromServer();
}
```

Now the image is loaded from the server.

The memory state becomes:

```text
image2
   ↓
ImageProxy
   fileName = "photo2.jpg"
   realImage ─────────────→ RealImage
                              fileName = "photo2.jpg"
```

* * *

## Step 5: The proxy delegates the request

```java
realImage.display();
```

The actual display operation is performed by `RealImage`.

```text
ImageProxy receives the request
            ↓
ImageProxy creates RealImage
            ↓
ImageProxy calls RealImage.display()
            ↓
RealImage displays the image
```

* * *

# What Happens on the Second Call?

Suppose we call `display()` twice:

```java
image2.display();
image2.display();
```

Output:

```text
photo2.jpg is loading from the server
photo2.jpg is displayed
photo2.jpg is displayed
```

The image is loaded only once.

During the second call:

```java
if (realImage == null)
```

The condition becomes false because `realImage` already exists.

Therefore, the proxy reuses the existing object:

```java
realImage.display();
```

This prevents duplicate loading.

* * *

# Why Not Create the Real Object Directly at Display Time?

We could write:

```java
new RealImage("photo2.jpg").display();
```

This is perfectly valid when:

*   There is only one image
    
*   The image must be displayed immediately
    
*   The object is not expensive
    
*   The reference does not need to be stored
    
*   No access control or caching is required
    

In such a simple situation, Proxy Pattern is unnecessary.

However, real applications often need to create and store a list of objects before knowing which objects will actually be used.

For example:

```java
List<Image> images = new ArrayList<>();

images.add(new ImageProxy("photo1.jpg"));
images.add(new ImageProxy("photo2.jpg"));
images.add(new ImageProxy("photo3.jpg"));
```

Later, the client can simply write:

```java
images.get(1).display();
```

The client does not need to manage object creation manually.

Without a proxy, the client might have to maintain this logic:

```java
if (realImage == null) {
    realImage = new RealImage(fileName);
}

realImage.display();
```

If this logic is required in many places, it creates duplication and makes the client responsible for managing the real object.

The proxy hides this complexity.

The client only needs to say:

```java
image.display();
```

The proxy decides:

```text
Does the real object exist?
        ↓
No → Create it and use it
Yes → Reuse the existing object
```

* * *

# Where Does This Situation Occur in Real Applications?

## 1\. E-commerce Product Listing

Suppose an e-commerce page contains 100 products.

The application may already know:

*   Product name
    
*   Product ID
    
*   Price
    
*   Image URL
    
*   Discount
    

However, only the first few products are visible on the screen.

```text
Products 1–6   → Visible, load their images
Products 7–100 → Not visible, wait for scrolling
```

The application stores all the image URLs but downloads an image only when its product card approaches the screen.

This improves:

*   Page loading speed
    
*   Mobile performance
    
*   Bandwidth usage
    
*   Memory consumption
    

* * *

## 2\. Social Media Feed

An Instagram-like feed may contain many posts.

The application receives metadata for all posts:

```text
Post ID
Caption
Likes
Image URL
User information
```

However, only a few posts are visible at one time.

Lazy loading ensures that images are downloaded when the user scrolls near them.

* * *

## 3\. PDF Viewer

A PDF may contain 500 pages.

Rendering all 500 pages immediately would be expensive.

A page proxy can initially store only the page number:

```java
Page page = new PageProxy(200);
```

The actual page is rendered only when the user navigates to page 200:

```java
page.display();
```

* * *

## 4\. Database Relationships

Suppose a customer has 10,000 orders.

When loading the customer, we may only need the customer’s name:

```java
Customer customer =
        customerRepository.findById(10);

customer.getName();
```

Fetching all 10,000 orders at the same time would be wasteful.

The orders can be loaded when the application calls:

```java
customer.getOrders();
```

ORM frameworks such as Hibernate can use proxy objects internally for this type of lazy loading.

* * *

# Roles in the Proxy Pattern

| Component | Example | Responsibility |
| --- | --- | --- |
| Subject | `Image` | Defines the common contract |
| Real Subject | `RealImage` | Performs the actual expensive work |
| Proxy | `ImageProxy` | Controls access and performs lazy loading |
| Client | `Main` | Uses the object through the interface |

* * *

# Class Relationship

```text
                   Image
                display()
                 /     \
                /       \
       RealImage       ImageProxy
                            |
                            |
                            ↓
                       RealImage
```

Both `RealImage` and `ImageProxy` implement the same `Image` interface.

The proxy also keeps a reference to `RealImage`.

* * *

# Different Types of Proxy

## 1\. Virtual Proxy

A Virtual Proxy delays the creation of an expensive object.

Examples:

*   Image lazy loading
    
*   PDF page rendering
    
*   Video thumbnail loading
    
*   Large document loading
    

Our image example is a Virtual Proxy.

* * *

## 2\. Protection Proxy

A Protection Proxy checks permissions before allowing access.

```text
User → Permission check → Real service
```

Examples:

*   Admin-only operations
    
*   Bank PIN verification
    
*   File permissions
    
*   Employee role authorization
    

* * *

## 3\. Caching Proxy

A Caching Proxy stores the result of an expensive operation.

```text
Client → Proxy checks cache → Real API
```

If the result already exists in the cache, the proxy returns it without calling the real service again.

* * *

## 4\. Remote Proxy

A Remote Proxy represents an object located on another server.

```text
Application → Remote Proxy → Remote Server
```

The client uses the proxy like a local object, while the proxy handles network communication.

* * *

## 5\. Logging Proxy

A Logging Proxy records information before or after calling the real object.

```text
Client
  ↓
Logging Proxy
  ↓
Real Service
```

It may record:

*   Method name
    
*   Request time
    
*   Response time
    
*   Errors
    
*   User information
    

* * *

# Proxy Pattern vs Decorator Pattern

Proxy and Decorator may look similar because both wrap another object.

However, their intentions are different.

| Proxy Pattern | Decorator Pattern |
| --- | --- |
| Controls access to an object | Adds new behaviour to an object |
| Used for lazy loading, security and caching | Used for combining additional features |
| May create the real object internally | Usually receives the wrapped object |
| Manages when and how an object is accessed | Enhances what the object can do |

Example of Proxy:

```text
Can this user access the bank account?
```

Example of Decorator:

```text
Add milk and sugar to the coffee.
```

A simple way to remember:

> Proxy controls the object. Decorator enhances the object.

* * *

# Advantages of Proxy Pattern

*   Supports lazy initialization
    
*   Reduces unnecessary object creation
    
*   Saves memory and network bandwidth
    
*   Adds security without changing the real object
    
*   Can provide caching and logging
    
*   Keeps additional access logic away from the client
    
*   The client can use Proxy and Real Object through the same interface
    

* * *

# Disadvantages of Proxy Pattern

*   Adds additional classes
    
*   Makes the execution flow slightly more complex
    
*   Adds an extra layer between client and real object
    
*   Incorrect proxy logic may make debugging difficult
    
*   In some situations, the proxy can introduce a small performance overhead
    

Therefore, Proxy Pattern should be used when access control or delayed creation provides a real benefit.

* * *

# When Should We Use Proxy Pattern?

Use Proxy Pattern when:

*   Creating the real object is expensive
    
*   The real object should be loaded only when required
    
*   Permission must be checked before access
    
*   Results should be cached
    
*   Calls should be logged
    
*   The real object exists on a remote server
    
*   The client should not manage object creation directly
    

Do not use it simply because an object can be wrapped. Use it when you need to control access to the real object.

* * *

# Interview Explanation

You can explain Proxy Pattern in an interview like this:

> Proxy Pattern is a structural design pattern that provides a substitute for a real object. The proxy implements the same interface as the real object and controls access to it. It can perform lazy loading, security checks, caching or logging before delegating the actual operation to the real object.

The most important flow is:

```text
Client → Proxy → Real Object
```

For lazy loading:

```text
Create lightweight Proxy
          ↓
Store real object as null
          ↓
Client calls display()
          ↓
Proxy checks the real object
          ↓
Create it only when required
          ↓
Delegate the operation
```

* * *

# Conclusion

The main purpose of Proxy Pattern is not to replace the real object permanently. Its purpose is to control access to the real object.

In our image example:

*   `ImageProxy` stores lightweight image information.
    
*   `RealImage` performs the expensive loading operation.
    
*   The real image is created only when `display()` is called.
    
*   Once created, the same real object is reused.
    

The entire concept can be remembered in one sentence:

> A Proxy stands between the client and the real object and decides when and how the real object should be accessed.
