Chaturmind
LearnDSASystem DesignBlogPremium
Sign inGet started
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML

Company

  • Blog
  • Premium
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
HomeLearnSystem DesignLow-Level Design & Design PatternsStructural Design Patterns
✓ FreeIntermediate· 7 min read

Composite & Proxy

Composite treats a single item and a group of items uniformly through a file-system exercise; Proxy controls access to an object for lazy loading, caching, or access control.

Published September 22, 2026


Composite & Proxy

Composite: treating one thing and a group of things the same way

A file system is the canonical case: a File is a single item, a Directory is a collection of items (which may themselves be files or more directories). Without Composite, client code has to keep checking "is this a file or a directory?" every time it wants to compute something recursive like total size.

interface FileSystemComponent {
    long size();
    String name();
}

class File implements FileSystemComponent {
    private final String name;
    private final long sizeBytes;

    File(String name, long sizeBytes) { this.name = name; this.sizeBytes = sizeBytes; }
    public long size() { return sizeBytes; }
    public String name() { return name; }
}

class Directory implements FileSystemComponent {
    private final String name;
    private final List<FileSystemComponent> children = new ArrayList<>();

    Directory(String name) { this.name = name; }
    void add(FileSystemComponent child) { children.add(child); }

    public long size() {
        return children.stream().mapToLong(FileSystemComponent::size).sum(); // recurses into sub-directories transparently
    }
    public String name() { return name; }
}
Directory root = new Directory("project");
root.add(new File("README.md", 2_048));
Directory src = new Directory("src");
src.add(new File("Main.java", 4_096));
root.add(src);

root.size(); // 6144 — computed uniformly, caller never checks File vs Directory

The key property: calling code treats a File and a Directory identically, through the same FileSystemComponent interface — root.size() recurses into arbitrarily deep nesting without the caller writing any recursive tree-walking logic itself. Directory.size() is where the recursion actually lives, and it's naturally recursive because a Directory's children are themselves FileSystemComponents, which might be more directories.

Proxy: controlling access, not adding behavior

Proxy implements the same interface as a real subject, but stands in front of it to control access — for lazy initialization, permission checks, caching, or remote communication.

interface Image { void display(); }

class RealImage implements Image {
    private final String filename;
    RealImage(String filename) {
        this.filename = filename;
        loadFromDisk(); // expensive — happens immediately on construction
    }
    private void loadFromDisk() { /* slow I/O */ }
    public void display() { System.out.println("Displaying " + filename); }
}

// Lazy-loading proxy: defers the expensive load until actually needed
class ImageProxy implements Image {
    private final String filename;
    private RealImage realImage; // null until first display()

    ImageProxy(String filename) { this.filename = filename; }

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename); // expensive load, deferred to first use
        }
        realImage.display();
    }
}

Callers hold an Image reference and call display() exactly as they would on a RealImage — the laziness is completely invisible to them. Other Proxy variants follow the same shape with a different purpose behind the same interface: an access-control proxy checks permissions before delegating; a remote proxy forwards calls across a network, hiding the RPC mechanics; a caching proxy returns a cached result instead of delegating, when available.

Composite vs Proxy — same wrapping shape, opposite intent

Both Composite and Proxy (and Decorator) implement the same interface they wrap or contain — structurally similar. The difference is intent: Composite's point is treating individual and grouped objects uniformly (a tree of many real objects); Proxy's point is controlling access to a single object, which may not even be constructed yet. Neither adds new behavior the way Decorator does — Composite aggregates, Proxy gatekeeps.

Follow-up questions this topic invites — and their answers

Q: How would you remove a file from a Directory in the Composite example, and what design question does that raise? A: Adding a remove(FileSystemComponent) method to the interface raises the classic Composite tension: should File (a leaf with no children) also implement remove()/add() even though they're meaningless for it? Common resolutions are throwing UnsupportedOperationException from leaf nodes, or splitting the interface so only Directory-like composites expose child-management methods (an application of Interface Segregation — see Liskov Substitution & Interface Segregation).

Q: Is Java's java.sql.Connection pooling library implementation typically a Proxy? A: Yes — a pooled connection is usually a proxy around a real Connection: calling close() on it doesn't actually close the underlying connection, it returns it to the pool, which is exactly a proxy intercepting a call to redirect its real effect.

Q: What's the difference between a caching Proxy and simply adding a cache field inside the real object itself? A: A caching Proxy keeps caching as a separable, removable concern that the real object's class knows nothing about (matching Single Responsibility) — you can drop the proxy in front of any implementation of the interface without modifying it, whereas a cache field baked into the real class couples caching logic to business logic permanently.

Q: Can Composite and Proxy be combined? A: Yes — e.g. a Directory whose remote/network-mounted children are represented by remote proxies, so size() transparently triggers network calls for remote parts of the tree while local parts resolve immediately, all through the same uniform FileSystemComponent interface.

Previous

Decorator Pattern

Next

Bridge & Flyweight

AI Tutor

Lesson: Composite & Proxy

Quick actions

AI responses can be inaccurate. Verify critical information.