The Problem It Solves
ArrayList<String> names = new ArrayList<>(); // caller knows the concrete
names.sort(...);
// three months later: needs dedup on insert → HashSet? LinkedHashSet?
// every declared type, every parameter, every field must be retyped.
Declaring variables and parameters as concrete classes welds callers to today’s implementation choice. The coupling is invisible until the day the implementation must change — then it becomes a sweeping refactor. The principle: depend on contracts (List, Map, domain interfaces); let construction sites — ideally exactly one — know the concrete.
The Mechanism
Caller code Interface (contract) Implementations
┌──────────────────┐ ┌───────────────────────┐ ┌──────────────────┐
│ List<String> s; │ ────► │ List / Sorter / Store │ ◄──── │ ArrayList │
│ uses add/get/... │ │ (stable surface) │ │ LinkedList │
└──────────────────┘ └───────────────────────┘ │ CopyOnWriteList │
└──────────────────┘
Caller sees only the middle box. Right side can grow or swap freely.
The single rule in code:
List<String> names = new ArrayList<>(); // declare LEFT type as interface
void register(Sorter sorter) // parameters: interfaces too
new ArrayList<>() still exists — but confined to one line. Swapping implementations becomes a one-line change plus zero caller edits.
What It Buys
- Swappability: storage, algorithms, vendors replaceable behind stable seams.
- Testability: any collaborater behind an interface can be stubbed — no framework required for a test seam.
- Parallel work: teams code against agreed interfaces before implementations exist.
- JDK ubiquity proves the point: nearly every JDK API returns
List/Map/Set, neverArrayList/HashMap— Sun could change internal implementations across decades without breaking user code.
What It Costs
- Indirection when reading: “what does
get()actually do?” requires finding the implementation. - An interface with one implementation and no planned variant and no test purpose is pure ceremony.
- Performance nuance: megamorphic call sites lose JIT inlining (see vtable page) — irrelevant for most code, real for hot loops.
Where NOT to Apply
| Situation | Verdict |
|---|---|
| Domain concept with known variants (payment modes, storages) | Interface |
| Collaborator needing test stubs | Interface |
| Internal helper class, private to one module | Skip — direct class |
Value types with no variation (Money) | Skip |
| DTOs crossing boundaries | Skip — data, not behavior |
Interview Signals
- Field and parameter declarations using interfaces on the whiteboard is visible instantly and scored.
- The senior qualifier: naming why this beats always-interface (“seams cost reading effort; buy them only where change or testing is expected”).
Premium Content
Unlock Program to Interfaces and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans