JavaIntermediate

Java Interview: OOP, SOLID & Design Patterns

The design round: polymorphism and abstraction explained the way interviewers want to hear it, SOLID applied to real code, the handful of patterns that get asked, immutability, and generics.

4 sections · ~32 min · 5-question quiz (pass ≥ 70%)

1The Four Pillars — Answered Well

Reciting definitions scores nothing. Show the why and give a concrete example.

Encapsulation — hide state, expose behaviour. Why: you can change internals without breaking callers.

public class Account {
    private BigDecimal balance;               // nobody can set this directly

    public void withdraw(BigDecimal amount) { // invariants enforced in one place
        if (amount.compareTo(balance) > 0) throw new InsufficientFundsException();
        balance = balance.subtract(amount);
    }
}

Abstraction — expose what, hide how. List is the abstraction; ArrayList is one implementation.

Inheritance — an "is-a" relationship for shared behaviour. Use it sparingly; favour composition over inheritance, because subclasses are coupled to their parent's implementation details.

Polymorphism — one interface, many behaviours. Distinguish the two kinds:

  • Runtime (overriding): which method runs is decided by the object's type at execution time.
  • Compile-time (overloading): which method is selected is decided by the declared argument types at compile time.
Shape s = new Circle(5);
s.area();     // Circle.area() — runtime dispatch on the actual object

Abstract class vs interface — the perennial question:

Abstract class Interface
State Can hold fields Only static final constants
Constructors Yes No
Multiple inheritance No Yes
Use when Sharing implementation among close relatives Defining a capability many unrelated types can have

Since Java 8 interfaces can carry default and static methods, so the practical answer is: interface by default; abstract class when you must share state.

2SOLID, With Code That Fails and Code That Passes

S — Single Responsibility. One reason to change. A class that parses a CSV, validates it, and writes to the DB changes for three reasons — split it.

O — Open/Closed. Open for extension, closed for modification. The tell is a growing if/else on type:

// Violates OCP — every new type edits this method
double fee(Payment p) {
    if (p.type == CARD) return p.amount * 0.02;
    if (p.type == UPI)  return 0;
    ...
}

// Follows OCP — add a new strategy without touching existing code
interface FeePolicy { BigDecimal fee(BigDecimal amount); }

L — Liskov Substitution. A subtype must be usable wherever the base type is. The canonical violation: Square extends RectanglesetWidth on a Square must also change the height, breaking every caller that assumes independence.

I — Interface Segregation. Many small interfaces beat one fat one. If implementers throw UnsupportedOperationException for half the methods, the interface is too big.

D — Dependency Inversion. Depend on abstractions, not concretions.

// Rigid — cannot be unit-tested without a real database
class OrderService { private final MySqlOrderRepo repo = new MySqlOrderRepo(); }

// Inverted — inject the abstraction; swap in a fake for tests
class OrderService {
    private final OrderRepository repo;
    OrderService(OrderRepository repo) { this.repo = repo; }
}

That last example is also the answer to "why do we use dependency injection?" — testability and swappable implementations. Constructor injection is preferred: it makes dependencies explicit and lets fields be final.

3The Patterns That Get Asked

Five patterns cover the overwhelming majority of Java interview questions.

Singleton — one instance. The safe implementations are the enum and the holder idiom:

public enum Config { INSTANCE; }              // thread-safe, serialization-safe

public class Config {                          // lazy holder idiom
    private Config() {}
    private static class Holder { static final Config I = new Config(); }
    public static Config getInstance() { return Holder.I; }
}

Be ready for "what's wrong with singletons?" — hidden global state, hard to test, hard to parallelise.

Factory — centralise object creation behind a method, so callers depend on the interface, not the concrete class.

Builder — for objects with many optional parameters; avoids telescoping constructors and enables immutability:

Pizza p = new Pizza.Builder().size(12).cheese(true).olives(true).build();

Strategy — swap an algorithm at runtime. In modern Java it is often just a lambda:

list.sort(Comparator.comparing(Employee::getName));   // Strategy, implicitly

Observer — publish/subscribe; one-to-many notification. Underlies event listeners and reactive streams.

Also worth a sentence each: Decorator (wrap to add behaviour — BufferedReader wrapping FileReader), Adapter (translate one interface into another), Template Method (base class fixes the skeleton, subclasses fill the steps).

How to answer a pattern question well: name the problem it solves, sketch five lines of code, then name a real class from the JDK or Spring that uses it. That third part is what most candidates skip.

4Immutability, Generics, and Modern Java

Immutable objects are thread-safe for free, safe as map keys, and cannot be corrupted by a caller.

public final class Money {                    // final: no subclass can break it
    private final BigDecimal amount;          // final fields
    private final List<String> tags;

    public Money(BigDecimal amount, List<String> tags) {
        this.amount = amount;
        this.tags = List.copyOf(tags);        // defensive copy IN
    }

    public List<String> getTags() { return tags; }   // already unmodifiable
}

The two things candidates forget: defensive copies of mutable inputs, and no setters. A final field holding an ArrayList is still mutable through its reference.

Records collapse all of that:

public record Money(BigDecimal amount, String currency) {}

Records give you final fields, a canonical constructor, equals, hashCode, and toString. You can add a compact constructor for validation.

Generics exist for compile-time type safety and to remove casts. The key interview point is type erasure: generics are checked at compile time and erased at runtime, so List<String> and List<Integer> are the same class at runtime, and you cannot write new T[10].

PECS — Producer Extends, Consumer Super:

void copy(List<? extends Number> src,   // producer: we READ Numbers from it
          List<? super Number> dst)     // consumer: we WRITE Numbers into it

Modern Java worth name-dropping (it signals you have kept current):

var users = new ArrayList<User>();              // 10: local type inference
String s = """
    text block""";                              // 15
if (o instanceof User u && u.isActive()) {}     // 16: pattern matching
sealed interface Shape permits Circle, Square {} // 17: exhaustive hierarchies

Ready to test yourself?

Sign in to take the quiz, track progress, and earn a certificate.

Sign in