Realization: implementing interface contracts
Understand realization in OOP, the relationship where a class implements an interface, promising to fulfill its contract with concrete behavior.
You define a Sortable interface with a sort() method. Three different classes implement it. Six months later, a junior developer adds a fourth class, implements the interface, and leaves the sort() method body completely empty. The code compiles. The tests pass (because nobody tested the new class). Production breaks silently when data arrives unsorted. That is the cost of misunderstanding realization: you can promise to fulfill a contract and deliver nothing.
What Is Realization
Realization is the relationship between an interface and a class that implements it. The interface defines a contract (method signatures, expected behaviors). The implementing class promises to provide concrete behavior for every method in that contract. In Java, this is the implements keyword.
Think of it like a franchise agreement. The franchisor (interface) says: "You must serve coffee, sandwiches, and pastries. Here are the menu items you must offer." The franchisee (implementing class) agrees to that contract and decides exactly how to make each item. Different franchisees can have different recipes, but every single one must serve everything on the menu.
For your interview: realization is the "fulfills" relationship. A class does not inherit behavior from an interface. It promises to provide that behavior from scratch. That distinction between inheriting code and fulfilling a contract is exactly what separates realization from inheritance.
Contract, not code
An interface provides zero implementation (ignoring default methods for now). The implementing class provides all of it. This is the fundamental nature of realization: the interface says what must be done, the class says how.
UML Notation
Realization uses a dashed line with a hollow (open) arrowhead pointing from the implementing class to the interface. The dashed line signals "this is not inheritance" (inheritance uses a solid line). The hollow arrowhead points toward the interface being fulfilled.
Compare the three arrow types that involve inheritance-like upward relationships:
| Relationship | UML Arrow | Line Style | Arrowhead | Java Keyword |
|---|---|---|---|---|
| Realization | ..|> | Dashed | Hollow triangle | implements |
| Inheritance | --|> | Solid | Hollow triangle | extends (class) |
| Dependency | ..> | Dashed | Open arrow | Parameter/local usage |
The dashed line is the visual cue that separates realization from inheritance. If you draw a solid line from BubbleSort to Sortable, you are telling the reader "BubbleSort extends Sortable," which means inherited code. The dashed line correctly says "BubbleSort fulfills the Sortable contract." I see candidates mix these up on whiteboard interviews constantly.
Notice how both Employee and Contractor realize two interfaces. Each class provides completely different implementations of calculatePay() (salary vs hourly rate), but both honor the same contract. This is the power of realization: the calling code can work with any Payable without knowing which concrete class it is dealing with.
Realization vs Inheritance
This is the comparison that comes up in every OOP interview. Both involve an "is-a" relationship, but the mechanism is different.
| Dimension | Inheritance | Realization |
|---|---|---|
| Java keyword | extends | implements |
| Parent type | Class (or abstract class) | Interface |
| Code inherited? | Yes (method bodies, fields) | No (only method signatures) |
| Multiple allowed? | No (single inheritance in Java) | Yes (a class can implement many interfaces) |
| UML line | Solid | Dashed |
| Coupling strength | Strong (changes to parent ripple down) | Weaker (interface rarely changes) |
| Purpose | Reuse code and establish type hierarchy | Define a contract for polymorphism |
The practical rule: use inheritance when you have shared behavior that child classes should reuse. Use realization when you want to guarantee a class supports a specific capability without dictating how.
I use this heuristic in design reviews: if two classes share 80% of their method implementations, inheritance makes sense. If they share only method signatures but the bodies are completely different, realization is the right relationship. BubbleSort and QuickSort both have a sort() method, but the algorithms are entirely different, so realization through a Sortable interface is the correct design.
Interview shortcut
When an interviewer asks "inheritance vs realization," answer with this: "Inheritance gives you code for free but locks you into one parent. Realization gives you no code but lets you implement multiple contracts. In practice, I default to interfaces and only use inheritance when there is genuine shared behavior to reuse."
Implementation
// The interface defines the contract. Any class that implements
// Sortable promises to provide a working sort() method.
// The interface says WHAT, not HOW.
public interface Sortable {
/**
* Sorts the given list and returns a new sorted list.
* Implementations must not modify the original list.
*/
List<Integer> sort(List<Integer> data);
}The key observation: the Main class never mentions BubbleSort, QuickSort, Employee, or Contractor directly after construction. It works entirely through the Sortable and Payable interfaces. That is the whole point of realization. You can add a MergeSort class tomorrow, and Main does not change at all.
Multiple Realization
Java allows a class to implement as many interfaces as it needs. This is one of the biggest practical advantages realization has over inheritance, where Java restricts you to a single parent class.
Each interface grants a capability: Serializable means the transaction can be converted to a string for storage, Comparable means transactions can be sorted by timestamp, Auditable means the transaction tracks its own history. The class fulfills all three contracts independently.
The practical benefit: different parts of the system can accept Transaction through different type lenses. A persistence layer accepts anything Serializable. A sorting utility accepts anything Comparable. An audit service accepts anything Auditable. The same object participates in all three contexts without any of those systems knowing about each other.
Interface explosion
Multiple realization is powerful, but it has a limit. If a class implements 8+ interfaces, it is probably doing too many things. Each interface is a separate contract, and fulfilling all of them in a single class can make that class a dumping ground. When you see a class with more than 4 or 5 interfaces, check whether it should be split.
Sealed Interfaces (Java 17+)
Java 17 introduced sealed interfaces, which let you restrict which classes can implement an interface. This is useful when you want the contract to exist but need to control who fulfills it.
public sealed interface Shape
permits Circle, Rectangle, Triangle {
double area();
double perimeter();
}
Only Circle, Rectangle, and Triangle can implement Shape. Any other class attempting implements Shape gets a compile-time error. This gives you exhaustive pattern matching in switch expressions:
public String describe(Shape shape) {
return switch (shape) {
case Circle c -> "Circle with radius " + c.radius();
case Rectangle r -> "Rectangle " + r.width() + "x" + r.height();
case Triangle t -> "Triangle with base " + t.base();
};
// No default needed: compiler knows all cases are covered
}
Sealed interfaces combine the contract guarantee of realization with compile-time exhaustiveness. The interface still says "what," the implementing classes still say "how," but now the set of implementors is closed and known at compile time.
For your interview: mention sealed interfaces when discussing domain models with a fixed set of types. Payment methods (CreditCard, BankTransfer, Wallet), notification channels (Email, Sms, Push), or AST node types are all natural fits.
Common Mistakes
Mistake 1: Empty implementations that satisfy the compiler but not the contract.
public class LazySort implements Sortable {
@Override
public List<Integer> sort(List<Integer> data) {
return data; // Returns unsorted data. Compiles fine. Breaks everything.
}
}
The compiler only checks that the method signature exists. It cannot verify that sort() actually sorts. This is the most dangerous form of realization failure because it is invisible until runtime. Every implementation must genuinely fulfill the contract's semantic intent, not just its syntactic shape.
Mistake 2: Interface pollution (fat interfaces).
public interface Worker {
void code();
void review();
void deploy();
void attendMeeting();
void writeDocumentation();
void onCallRotation();
}
Not every worker does all of these things. An intern should not be forced to implement onCallRotation() with an empty body. This violates the Interface Segregation Principle. Split fat interfaces into focused ones: Coder, Reviewer, OnCallRotatable. Each implementing class picks only the interfaces that genuinely apply.
Mistake 3: Confusing realization with inheritance on UML diagrams.
Drawing a solid line from BubbleSort to Sortable tells the reader "BubbleSort extends Sortable and inherits code." The correct notation is a dashed line with a hollow arrowhead. Getting this wrong changes the design conversation because inheritance implies shared code, while realization implies only a shared contract.
Mistake 4: Using abstract classes when interfaces would suffice.
If the parent type has no shared implementation (zero method bodies, zero fields), it should be an interface, not an abstract class. Abstract classes consume your single inheritance slot. Interfaces leave that slot open for cases where you genuinely need shared code via inheritance.
Mistake 5: Not using @Override annotation.
public class BubbleSort implements Sortable {
// Missing @Override: if the interface method signature changes,
// this becomes a NEW method instead of a compile error.
public List<Integer> sort(List<Integer> data) { ... }
}
Without @Override, renaming the interface method from sort() to sortData() does not break the implementing class at compile time. The old sort() method silently becomes a standalone method. Always use @Override so the compiler catches contract mismatches immediately.
Test Your Understanding
Quick Recap
- Realization is the relationship where a class implements an interface, promising to provide concrete behavior for every method in the contract.
- UML notation: dashed line with a hollow arrowhead (
..|>) from the implementing class to the interface. Never use a solid line, that means inheritance. - Java uses the
implementskeyword for realization andextendsfor inheritance. Realization carries no code, only a contract. - A class can realize multiple interfaces (unlike single inheritance), which is the primary mechanism for combining capabilities in Java.
- Empty implementations are the most dangerous mistake: the compiler is satisfied, but the contract is broken silently at runtime.
- Sealed interfaces (Java 17+) close the set of implementors, enabling exhaustive
switchexpressions while still preserving the contract-based design. - In interviews, default to interfaces over abstract classes. Use inheritance only when there is genuine shared code to reuse.