Showing posts with label OOP Concepts. Show all posts
Showing posts with label OOP Concepts. Show all posts

Composite Design Pattern in Java: Complete Guide with Examples (2026)

Composite Design Pattern in Java: Complete Guide with Examples

Learn the Composite design pattern in Java with simple explanations and practical examples. This guide covers how to treat single objects and tree structures uniformly, with before-and-after code for beginners and experts.

I've been writing Java for years, and Composite is the pattern I reach for whenever I see a tree. Not because trees are fancy—because the moment you write your first instanceof check inside a recursive loop, you know you'll regret it. Here's how Composite saved me from that trap.

Why the Composite Pattern Still Matters in Java

You've built a system where objects form a hierarchy—folders inside folders, departments inside departments, UI panels inside panels. Then a requirement arrives: "Calculate the total size of everything" or "Render the whole screen." Suddenly, your code is full of instanceof checks and recursive loops that break every time someone adds a new type.

The Composite pattern solves this by letting you treat a single object and a group of objects the exact same way. You call one method, and the tree handles itself.

Whether you're building file explorers, organization charts, or GUI frameworks, Composite keeps your code clean and your recursion automatic.

What is the Composite Design Pattern?

"Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly."

In plain words: Composite makes a container behave like the things it contains. A folder behaves like a file. A department behaves like an employee. A group of shapes behaves like a single shape.

The Three Players

Role Responsibility Example
Component Common interface for leaves and composites FileSystemItem
Leaf Individual object with no children File
Composite Container that holds children (leaves or other composites) Folder
Composite Design Pattern Structure Diagram Component interface extended by Leaf (File) and Composite (Folder). Composite holds a list of Component children and delegates operations recursively. COMPOSITE PATTERN STRUCTURE «interface» FileSystemItem + getSize() / display() File (Leaf) + getSize() → bytes Folder (Composite) + getSize() → sum(children) holds children Component (common interface) Concrete classes Recursive composition

Composite pattern structure: Folder (Composite) holds a list of FileSystemItem children, which can be Files (Leaf) or other Folders.

How to read this diagram (for freshers): The blue box at the top is the Component—the common interface both files and folders share. The yellow boxes below are the two concrete types. The File on the left is a Leaf—it has no children, so its getSize() just returns its own bytes. The Folder on the right is a Composite—it holds a list of children. When you call getSize() on a Folder, it loops through its children and calls getSize() on each one. If a child is another Folder, the recursion continues automatically. That self-referencing red arrow is the magic: a Composite can contain other Composites.

The Composite implements the same interface as the Leaf and holds a list of children. When you call a method on the Composite, it forwards the call to every child—recursively.

Real-World Example for Freshers

Before we touch code, let's look at the Composite pattern you already use every day on your phone and computer.

Example 1: Your Phone's File Manager

Open the file manager on any Android phone. You see folders and files. Inside a folder, there can be more folders and more files. Now tap "Properties" on a folder—it shows the total size of everything inside, including all the nested folders.

How does the phone calculate that total? It doesn't care whether something is a file or a folder. It asks each thing the same question: "How much space do you take?"

  • A file answers with its own size.
  • A folder asks each of its children the same question, sums the answers, and reports back.

That's it. That's the Composite pattern. Your phone doesn't have a special algorithm for "folder inside folder inside folder." It just repeats the same question down the tree.

Example 2: A Folder Structure You Already Know

📁 My Files
├── 📄 resume.pdf              (2 MB)
├── 📁 Photos
│   ├── 📄 sunset.jpg          (3 MB)
│   └── 📄 family.jpg          (4 MB)
└── 📁 Work
    ├── 📄 report.docx         (1 MB)
    └── 📁 Projects
        ├── 📄 design.pptx     (5 MB)
        └── 📄 budget.xlsx     (2 MB)

If you ask "How big is My Files?", the answer is 17 MB.

But notice—the Projects folder had to answer the question first, then Work had to sum report.docx and Projects, then My Files had to sum everything. Each folder asked its children the same question. That's the Composite pattern in real life.

Example 3: Online Shopping Cart

You're building an e-commerce app. A Product has a price. A Bundle (like "Back to School Combo") also has a price—but that price is the sum of everything in it, plus any discount. Inside the bundle, there can be another bundle.

  • Product (Leaf): returns its own price.
  • Bundle (Composite): loops through its items, asks each for its price, adds them up.

When the customer clicks "Total," the app doesn't write special logic for "bundle inside bundle." It calls getPrice() on the cart, and the tree does the rest.

Example 4: Drawing App (Shapes and Groups)

In tools like Figma or Canva, you can group shapes. A group can contain a rectangle, a circle, and even another group. If you drag the whole group, everything inside moves. If you resize the group, everything scales.

  • Rectangle, Circle (Leaves): know how to draw themselves and move themselves.
  • Group (Composite): tells each child "draw yourself" or "move by X pixels."

The software treats a group of 50 shapes exactly like a single shape. That's the whole point of the Composite pattern—uniformity.

Quick Summary for Freshers

Real-World Situation Leaf Composite
File manager File Folder
Shopping cart Product Bundle
Drawing app Rectangle, Circle Group
Company org chart Employee Department

Once you see it in your file manager, you'll see it everywhere. That's why Composite is one of the most practical design patterns in everyday software.

Violation: The Instanceof Nightmare

Imagine you're building a file explorer. You have File and Folder. To calculate total size, you write:

public long calculateSize(Object item) {
    if (item instanceof File) {
        return ((File) item).getBytes();
    } else if (item instanceof Folder) {
        long total = 0;
        for (Object child : ((Folder) item).getChildren()) {
            total += calculateSize(child); // Manual recursion
        }
        return total;
    }
    return 0;
}

The Problem: Every new item type (shortcut, zip archive, network drive) means editing this method. The client code knows too much about every concrete class. This is exactly what Composite eliminates.

Solution: Composite with Files and Folders

Let's build a file system where File and Folder share the same interface. The client calls getSize() once and the tree handles the rest.

Step 1: The Component Interface

public interface FileSystemItem {
    long getSize();
    void display(String indent);
}

Step 2: The Leaf (File)

public class File implements FileSystemItem {
    private String name;
    private long sizeInBytes;

    public File(String name, long sizeInBytes) {
        this.name = name;
        this.sizeInBytes = sizeInBytes;
    }

    @Override
    public long getSize() {
        return sizeInBytes;
    }

    @Override
    public void display(String indent) {
        System.out.println(indent + "File: " + name + " (" + sizeInBytes + " bytes)");
    }
}

Step 3: The Composite (Folder)

import java.util.ArrayList;
import java.util.List;

public class Folder implements FileSystemItem {
    private String name;
    private List<FileSystemItem> children = new ArrayList<>();

    public Folder(String name) {
        this.name = name;
    }

    public void add(FileSystemItem item) {
        children.add(item);
    }

    public void remove(FileSystemItem item) {
        children.remove(item);
    }

    @Override
    public long getSize() {
        long total = 0;
        for (FileSystemItem child : children) {
            total += child.getSize(); // Recursion happens here
        }
        return total;
    }

    @Override
    public void display(String indent) {
        System.out.println(indent + "Folder: " + name + "/");
        for (FileSystemItem child : children) {
            child.display(indent + "  ");
        }
    }
}

Step 4: Using the Composite

public class Demo {
    public static void main(String[] args) {
        // Build a tree
        Folder root = new Folder("root");

        File readme = new File("readme.txt", 1024);
        root.add(readme);

        Folder src = new Folder("src");
        src.add(new File("Main.java", 2048));
        src.add(new File("Utils.java", 1536));

        Folder tests = new Folder("tests");
        tests.add(new File("MainTest.java", 1024));

        root.add(src);
        root.add(tests);

        // One call — the tree handles recursion
        root.display("");
        System.out.println("Total size: " + root.getSize() + " bytes");
    }
}

Output:

Folder: root/
  File: readme.txt (1024 bytes)
  Folder: src/
    File: Main.java (2048 bytes)
    File: Utils.java (1536 bytes)
  Folder: tests/
    File: MainTest.java (1024 bytes)
Total size: 5632 bytes

What just happened? The client called root.getSize() once. The Folder looped through its children. When it hit src (another Folder), that Folder's getSize() ran the same loop. Recursion happened automatically—the client never wrote a single instanceof or manual recursive call.

For Beginners: The Box Analogy

Think of moving houses. A box (Folder) can contain items (Files) or smaller boxes (sub-Folders). To know how much everything weighs, you weigh each item. If you find a smaller box, you open it and weigh everything inside—recursively. You don't need a special procedure for "box inside box." The box handles it the same way every time.

Composite is the box. Inheritance would force you to write a different weighing procedure for every possible nesting combination.

For Experts: Composite vs. Decorator (Know the Difference)

This is a common interview trap.

Aspect Composite Decorator
Structure Tree (many children) Chain (one wrapped child)
Intent Aggregate part-whole hierarchy Augment behavior dynamically
Children 0 to many Exactly 1
Focus Structure Behavior

Rule of thumb: Composite is about many things acting as one. Decorator is about one thing wearing many hats.

Putting It All Together: The Organization Chart Example

A company has departments, and departments can contain employees or other departments. You need to calculate the total salary of the entire company. Without Composite, you'd write recursion with instanceof checks everywhere.

The Composite Structure

Role Concrete Class
Component OrganizationUnit
Leaf Employee
Composite Department
interface OrganizationUnit {
    double getTotalSalary();
    void print(String indent);
}

class Employee implements OrganizationUnit {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public double getTotalSalary() {
        return salary;
    }

    public void print(String indent) {
        System.out.println(indent + "- " + name + " ($" + salary + ")");
    }
}

class Department implements OrganizationUnit {
    private String name;
    private List<OrganizationUnit> members = new ArrayList<>();

    public Department(String name) {
        this.name = name;
    }

    public void add(OrganizationUnit unit) {
        members.add(unit);
    }

    public double getTotalSalary() {
        double total = 0;
        for (OrganizationUnit member : members) {
            total += member.getTotalSalary();
        }
        return total;
    }

    public void print(String indent) {
        System.out.println(indent + "+ " + name + "/");
        for (OrganizationUnit member : members) {
            member.print(indent + "  ");
        }
    }
}

Now adding Contractor or SubDepartment means one new class, not a matrix of combinations. The client code stays exactly the same.

Personal note: I once inherited a codebase where an org chart was modeled with a flat List<Employee> and a separate Map<Department, List<Employee>>. Any "total salary" calculation required three nested loops and two instanceof checks. Rewriting it with Composite cut the logic to one method—and made the tree self-evident in the code.

Frequently Asked Questions

What is the Composite design pattern in Java?

Composite is a structural pattern that composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects (leaves) and groups of objects (composites) uniformly through a common interface.

When should I use the Composite pattern?

Use Composite when your domain has a tree structure (file systems, organization charts, UI panels), when clients should ignore the difference between single objects and groups, or when you want to avoid instanceof checks and manual recursion.

How is Composite different from Decorator?

Composite aggregates many children into a tree and delegates operations to all of them. Decorator wraps exactly one object and adds behavior. Composite is about structure; Decorator is about augmenting behavior.

Does Composite support SOLID principles?

Yes. It supports Open/Closed (add new leaf or composite types without changing the client) and Liskov Substitution (leaves and composites are interchangeable through the Component interface).

Is Composite overkill for flat structures?

Yes. If your data never nests—a flat list of items with no children—Composite adds unnecessary indirection. Use it only when a hierarchy exists or is likely to grow.

Your Composite Checklist

Before committing to a tree structure, ask:

  • Am I writing instanceof checks? If you're checking type to decide how to process, Composite is probably the fix.
  • Does my data nest? If items can contain items of the same kind, you have a part-whole hierarchy.
  • Should clients care about depth? If the client wants to say "process everything" and let the tree handle it, Composite is right.
  • Am I mixing containers and content? If your lists hold "either a thing or a group of things," you're ready for Composite.

Start small. Refactor one tree-shaped domain. The clarity compounds.

Conclusion

The Composite pattern isn't about complexity—it's about uniformity. When a single object and a group of objects need to be treated the same way, Composite gives you one interface, one method call, and automatic recursion. It trades a small upfront design decision for long-term freedom from instanceof spaghetti.

I'll be honest: I don't use Composite in every project. Flat lists don't need it. But for anything that touches file systems, org charts, UI trees, or nested categories, it has saved me from rewriting the same recursion over and over. Start with the problem, not the pattern. If you find yourself writing if (item instanceof ...) inside a recursive loop, Composite is probably the answer.

Related topics: SOLID Principles in Java, Bridge Design Pattern, Decorator Design Pattern, Java Composition vs Inheritance, Structural Design Patterns

Bridge Design Pattern in Java: Complete Guide with Examples (2026)

Bridge Design Pattern in Java: Complete Guide with Examples

Learn the Bridge design pattern in Java with simple explanations and practical examples. This guide covers how to decouple abstraction from implementation using composition, with before-and-after code for beginners and experts.

I've been writing Java for years, and the Bridge pattern is one I keep coming back to. Not because it's fancy—because it saved me from a class explosion in a real payment system. Here's what I've learned.

Why the Bridge Pattern Still Matters in Java

You've built a class hierarchy. It works. Then requirements change: new platforms, new features, new combinations. Suddenly your class explosion looks like a family tree gone wrong.

The Bridge pattern solves this by splitting one tangled hierarchy into two independent ones. It's not about adding more classes—it's about organizing them so they can grow separately without stepping on each other.

Whether you're supporting multiple databases, payment gateways, or remote controls, Bridge keeps your design flexible without the inheritance nightmare.

What is the Bridge Design Pattern?

"Decouple an abstraction from its implementation so that the two can vary independently."

In plain words: Bridge prefers composition over inheritance. Instead of hardcoding every combination into a single class tree, you push implementation details into a separate object hierarchy and connect them through a reference.

The Two Hierarchies

Role Responsibility Example
Abstraction High-level control logic RemoteControl
Implementation Low-level platform-specific work Device (TV, Radio)
Bridge Design Pattern Structure Diagram Abstraction hierarchy (RemoteControl, AdvancedRemoteControl) connected via composition to Implementation hierarchy (Device interface with Tv and Radio). ABSTRACTION IMPLEMENTATION RemoteControl abstraction + togglePower() AdvancedRemoteControl + mute() extends «interface» Device + enable() / disable() Tv implements Radio implements THE BRIDGE Abstraction hierarchy Implementation hierarchy Composition (bridge)

Bridge pattern structure: RemoteControl and Device hierarchies grow independently through composition.

How to read this diagram (for freshers): Look at the left side — that's the Abstraction family (remotes). Look at the right side — that's the Implementation family (devices). The bold red arrow in the middle is the "bridge." The remote doesn't care which device it is holding — it just calls the methods defined in the Device interface. Add a new device (say, Projector)? Only the right side changes. Add a new remote (say, VoiceRemote)? Only the left side changes. Both sides grow without touching each other — that's the whole point of the pattern.

The abstraction holds a reference to an implementation object. When the abstraction needs work done, it delegates to that object. Since all implementations share a common interface, they're interchangeable.

Violation: The Class Explosion Nightmare

Imagine you're building a drawing app. You have two shape types (Circle, Square) and two rendering methods (Vector, Raster).

Without Bridge, inheritance forces you to create every combination:

// The explosion begins
class VectorCircle { /* ... */ }
class RasterCircle { /* ... */ }
class VectorSquare { /* ... */ }
class RasterSquare { /* ... */ }

// Add a new shape? Four more classes.
// Add a new renderer? Four more classes.

The Problem: Every new shape or renderer multiplies the class count. This is "nested generalizations"—a clear signal you need to split the hierarchy.

Solution: Bridge with Remote Controls and Devices

Let's use the classic remote control example. You have different remotes and different devices. A basic remote should work with a TV or a Radio. An advanced remote should do the same, plus extra features.

Step 1: The Implementation Hierarchy (Devices)

interface Device {
    boolean isEnabled();
    void enable();
    void disable();
    int getVolume();
    void setVolume(int volume);
    int getChannel();
    void setChannel(int channel);
}
class Tv implements Device {
    private boolean on = false;
    private int volume = 30;
    private int channel = 1;

    public boolean isEnabled() { return on; }
    public void enable() { on = true; }
    public void disable() { on = false; }
    public int getVolume() { return volume; }
    public void setVolume(int volume) { this.volume = volume; }
    public int getChannel() { return channel; }
    public void setChannel(int channel) { this.channel = channel; }
}

class Radio implements Device {
    // Similar implementation for radio
}

Step 2: The Abstraction Hierarchy (Remotes)

class RemoteControl {
    protected Device device;

    public RemoteControl(Device device) {
        this.device = device;
    }

    public void togglePower() {
        if (device.isEnabled()) {
            device.disable();
        } else {
            device.enable();
        }
    }

    public void volumeDown() {
        device.setVolume(device.getVolume() - 10);
    }

    public void volumeUp() {
        device.setVolume(device.getVolume() + 10);
    }
}

Step 3: Refined Abstraction (Advanced Remote)

class AdvancedRemoteControl extends RemoteControl {
    public AdvancedRemoteControl(Device device) {
        super(device);
    }

    public void mute() {
        device.setVolume(0);
    }
}

Step 4: Using the Bridge

public class Demo {
    public static void main(String[] args) {
        // Basic remote with TV
        Device tv = new Tv();
        RemoteControl basicRemote = new RemoteControl(tv);
        basicRemote.togglePower();
        basicRemote.volumeUp();

        // Advanced remote with Radio
        Device radio = new Radio();
        AdvancedRemoteControl advancedRemote = new AdvancedRemoteControl(radio);
        advancedRemote.togglePower();
        advancedRemote.mute();
    }
}

What just happened? The RemoteControl hierarchy and the Device hierarchy grew independently. Adding a new device (e.g., Projector) requires zero changes to remotes. Adding a new remote (e.g., VoiceRemote) requires zero changes to devices.

For Beginners: The Power Strip Analogy

Think of a power strip (the abstraction) and different appliances (the implementations). The power strip doesn't care if you plug in a lamp, a phone charger, or a fan. It just provides power. You can add new appliances without rewiring the strip, and you can buy a new power strip without replacing your appliances.

Bridge is the socket. Inheritance would be soldering the appliance directly to the wall.

For Experts: Bridge vs. Adapter (Know the Difference)

This is a common interview trap.

Aspect Bridge Adapter
Intent Decouple abstraction from implementation Make incompatible interfaces work together
Timing Designed upfront Retrofitted to existing code
Structure Two hierarchies, one reference One wrapper around one object
SOLID Directly supports OCP and DIP Supports SRP but is a band-aid

Rule of thumb: Bridge is planned separation. Adapter is emergency compatibility.

Putting It All Together: The Payment Processing Example

A payment system needs to support multiple gateways (Stripe, PayPal) and multiple payment types (Subscription, One-Time). Without Bridge, you'd need StripeSubscription, StripeOneTime, PayPalSubscription, PayPalOneTime—and it only gets worse with each addition.

The Bridge Structure

Abstraction (Payment Type) Implementation (Gateway)
Payment (abstract) PaymentGateway (interface)
SubscriptionPayment StripeGateway
OneTimePayment PayPalGateway
interface PaymentGateway {
    void process(double amount);
}

class StripeGateway implements PaymentGateway {
    public void process(double amount) {
        System.out.println("Stripe: $" + amount);
    }
}

abstract class Payment {
    protected PaymentGateway gateway;

    public Payment(PaymentGateway gateway) {
        this.gateway = gateway;
    }

    public abstract void pay(double amount);
}

class SubscriptionPayment extends Payment {
    public SubscriptionPayment(PaymentGateway gateway) {
        super(gateway);
    }

    public void pay(double amount) {
        gateway.process(amount * 0.9); // 10% discount
    }
}

class OneTimePayment extends Payment {
    public OneTimePayment(PaymentGateway gateway) {
        super(gateway);
    }

    public void pay(double amount) {
        gateway.process(amount);
    }
}

Now adding CryptoGateway or InstallmentPayment means one new class, not a matrix of combinations.

Personal note: I once inherited a codebase with StripeSubscription, StripeOneTime, PayPalSubscription, and PayPalOneTime. Adding Apple Pay would have meant four more classes. That's when I refactored to Bridge—and the next gateway took one class, not four.

Frequently Asked Questions

What is the Bridge design pattern in Java?

Bridge is a structural pattern that splits a large class or closely related classes into two independent hierarchies: abstraction and implementation. They connect through composition, allowing both to evolve without affecting each other.

When should I use the Bridge pattern?

Use Bridge when you have a proliferation of classes from combining two dimensions (e.g., shapes × renderers, payment types × gateways), or when you want to switch implementations at runtime.

How is Bridge different from Strategy?

Both use composition and delegation. Strategy swaps algorithms at runtime within a single class. Bridge separates two entire hierarchies that would otherwise be fused by inheritance. Bridge is structural; Strategy is behavioral.

Does Bridge support SOLID principles?

Directly. It supports Open/Closed (extend either hierarchy without modifying existing code) and Dependency Inversion (abstractions don't depend on concrete implementations).

Is Bridge overkill for small projects?

Yes, often. If you only have one implementation, or the dimensions won't grow, inheritance is simpler. Bridge pays off when you anticipate independent evolution on both sides.

Your Bridge Checklist

Before committing to a class hierarchy, ask:

  • Am I multiplying classes? If you have TypeA_Impl1, TypeA_Impl2, TypeB_Impl1—stop and consider Bridge.
  • Do I have two dimensions? If shapes and renderers both grow independently, split them.
  • Will implementations change? If you might swap databases or payment gateways, design for it now.
  • Am I ready for composition? Bridge requires accepting a reference in the constructor, not hardcoding it.

Start small. Refactor one exploding hierarchy. The clarity compounds.

Conclusion

The Bridge pattern isn't about complexity—it's about controlled growth. When two dimensions of your system need to evolve independently, Bridge gives them room to breathe. It trades a small upfront design decision for long-term freedom from class explosion.

I'll be honest: I don't use Bridge in every project. Small scripts don't need it. But for anything that touches payments, databases, or multiple platforms, it has saved me from rewriting the same logic over and over. Start with the problem, not the pattern. If you find yourself writing the same class name with different suffixes, Bridge is probably the answer.

Related topics: SOLID Principles in Java, Adapter Design Pattern, Strategy Design Pattern, Java Composition vs Inheritance, Structural Design Patterns

Java Static Block vs Instance Block vs Constructor: Output Explained

Java Static Block vs Instance Block vs Constructor: Output Explained

Java Static Block vs Instance Block vs Constructor: Output Explained

What is the output of the below program?

public class Sequence {
    Sequence() {
        System.out.print("c ");
    }
    {
        System.out.print("y ");
    }
    public static void main(String[] args) {
        new Sequence().go();
    }
    void go() {
        System.out.print("g ");
    }
    static {
        System.out.print("x ");
    }
}

Before you scroll down, write your answer. Most developers guess c y x g or x c y g. Both are wrong.

The correct output is: x y c g

Here is exactly why each letter prints in that order.

Step 1: x — the static block runs first

The static { } block runs once when the JVM loads the Sequence class. This happens before main starts, before any object is created. The block is written at the bottom of the source file, but Java does not care about visual position. Class loading fires it first.

Step 2: y — the instance initializer runs next

When new Sequence() executes, the JVM allocates the object and prepares to call the constructor. Before the constructor body runs, every instance initializer block executes. The { System.out.print("y "); } block is an instance initializer. The compiler copies it into every constructor, right after the implicit super() call.

Step 3: c — the constructor body runs

Only after the instance initializer finishes does the Sequence() constructor body execute. It prints c.

Step 4: g — the method call runs last

The object is fully constructed. go() is invoked and prints g.

Final output: x y c g

The four rules behind this order

Order What runs When How many times
1 Static initializer block When class is loaded Once per class loader
2 Instance initializer block Before constructor body, after super() Every object creation
3 Constructor body After instance initializers Every object creation
4 Method call When explicitly invoked As called

Follow-up: what if Sequence extends another class?

Interviewers rarely stop at the basic output. If Sequence extended a parent class Base, the order becomes:

  1. Parent static block
  2. Child static block
  3. Parent instance initializer
  4. Parent constructor
  5. Child instance initializer
  6. Child constructor
  7. Method call

The rule is simple: static members initialize from parent to child first, then instance members initialize from parent to child.

Why interviewers love this question

This single program tests four separate concepts at once:

  • Class loading vs object instantiation. Can you separate what happens once per class from what happens per object?
  • Static initialization timing. Do you know that static blocks run before main?
  • Initializer block semantics. Do you know that instance initializers run before constructor bodies, not after?
  • Source order vs execution order. The static block is at the bottom. Does that change anything? (No, for static blocks. Yes, for multiple instance blocks in the same class.)

A developer who gets this wrong likely does not understand the JVM lifecycle well enough to debug class-loading issues or write thread-safe singletons.

Where to go next

You now know why the output is x y c g. You know the inheritance variant. You know the rules.

If you want to go deeper, try this: add a static variable assignment before the static block, then reference that variable from main. Watch what changes. Or write a class with two constructors and one instance initializer. See how the initializer runs before both. The pattern will stick faster when you type it yourself.

And the next time someone shows you a "simple" output question, check the blocks first. The JVM has rules, and now you know them.