What are the SOLID principles?
Five design rules for classes that are easy to change:
- Single responsibility: one reason to change.
- Open/closed: add behavior with new classes, not edits.
- Liskov substitution: a subclass works anywhere its parent does.
- Interface segregation: no class implements methods it does not need.
- Dependency inversion: depend on abstractions, not concrete classes.
Explain the single responsibility principle with an example.
A class should have one reason to change. If tax rules, PDF layout and the database schema can each force edits to
Invoice, it has three responsibilities:// Before: three reasons to change class Invoice { double total() { ... } // business rules byte[] toPdf() { ... } // presentation void saveToDb() { ... } // persistence } // After: one each class Invoice { double total() { ... } } class InvoicePdfRenderer { byte[] render(Invoice inv) { ... } } class InvoiceRepository { void save(Invoice inv) { ... } }Now a layout change cannot break the tax calculation. Split when concerns change for different reasons, not into one-method classes.
What is the open/closed principle?
Classes should be open for extension and closed for modification: you add behavior by writing a new class, not by editing one that works.
Javapublic class Main { public static void main(String[] args) { Checkout checkout = new Checkout(); System.out.println(checkout.complete(new Upi(), 499)); System.out.println(checkout.complete(new Card(), 1299)); } } interface PaymentMethod { String pay(int amount); } class Upi implements PaymentMethod { public String pay(int amount) { return "UPI paid " + amount; } } class Card implements PaymentMethod { public String pay(int amount) { return "Card paid " + amount; } } class Checkout { String complete(PaymentMethod method, int amount) { return method.pay(amount); } }It prints
UPI paid 499andCard paid 1299. A wallet is one new class, andCheckoutstays untouched. The violation is anif (type.equals("upi"))chain insideCheckout.What does
resize(new Square())print in Java, and which SOLID rule breaks?Javapublic class Main { static void resize(Rectangle r) { r.setWidth(5); r.setHeight(4); System.out.println(r.area()); } public static void main(String[] args) { resize(new Square()); } } class Rectangle { protected int width, height; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } int area() { return width * height; } } class Square extends Rectangle { @Override void setWidth(int w) { width = w; height = w; } @Override void setHeight(int h) { width = h; height = h; } }Squarekeeps itself square by setting both sides in each setter, sosetHeight(4)also makes the width 4: the area is 16, not the 20resizeexpects. That breaks the Liskov substitution principle: a subtype must honor its parent's behavior, not only its signatures. Fix it with immutable shapes, or a sharedShapeinterface.What is the interface segregation principle?
No class should be forced to implement methods it does not use, so split a fat interface into small, focused ones:
// Fat: a robot must pretend to eat interface Worker { void work(); void eat(); } // Segregated interface Workable { void work(); } interface Eater { void eat(); } class Human implements Workable, Eater { ... } class Robot implements Workable { ... }The symptom of a violation is an implementation that throws
UnsupportedOperationExceptionor leaves a method empty.What is the difference between dependency inversion and dependency injection?
Dependency inversion is the principle: high-level code depends on an abstraction, not a concrete class. Dependency injection is one way to get there: the object receives its dependencies, usually through the constructor, instead of creating them with
new.Javaimport java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { FakeNotifier fake = new FakeNotifier(); new OrderService(fake).place("asha"); System.out.println(fake.sent); } } interface Notifier { void send(String to, String message); } class FakeNotifier implements Notifier { final List<String> sent = new ArrayList<>(); public void send(String to, String message) { sent.add(to + ": " + message); } } class OrderService { private final Notifier notifier; OrderService(Notifier notifier) { this.notifier = notifier; } void place(String user) { notifier.send(user, "order placed"); } }It prints
[asha: order placed]; a test passes a fake sender.What is composition, and why prefer composition over inheritance?
Composition builds a class from objects it holds (has-a) instead of extending one (is-a). Prefer it because inheritance exposes every public method of the parent and ties you to its implementation.
java.util.StackextendsVector, so it accepts list operations that break the stack:Javaimport java.util.Stack; public class Main { public static void main(String[] args) { Stack<Integer> stack = new Stack<>(); stack.push(1); stack.push(2); stack.add(0, 99); System.out.println(stack + " pop=" + stack.pop()); } }It prints
[99, 1, 2] pop=2. A stack that holds anArrayDequecannot be misused that way.Practice it: Implement Queue Using Stacks
What is the difference between association, aggregation and composition?
They differ in ownership and lifetime. Association: objects use each other and live independently (a teacher and a student). Aggregation: has-a, and the parts can outlive the whole (a team and its players). Composition: the whole owns its parts, and they die with it (a house and its rooms).
What are coupling and cohesion?
Cohesion is how closely the parts of one class belong together; coupling is how much a class depends on another's internals. Aim for high cohesion and low coupling.
OrderServicecreatingnew MySqlOrderDao()is tightly coupled; taking anOrderRepositoryinterface in its constructor is loose.How do you implement a thread-safe singleton in Java?
A singleton guarantees one instance with a global access point. The thread-safe versions are the enum singleton and the holder idiom; both rely on the JVM running class initialization once, under a lock.
Javapublic class Main { public static void main(String[] args) { System.out.println("start"); Config a = Config.get(); Config b = Config.get(); System.out.println(a == b); } } class Config { private Config() { System.out.println("loading config"); } private static class Holder { static final Config INSTANCE = new Config(); } static Config get() { return Holder.INSTANCE; } }It prints
start,loading config,true: created lazily, once. Double-checked locking works only with avolatilefield.What is the factory design pattern?
A factory is a method or class that creates objects, so callers ask for a notification for a channel and never name the concrete class. It keeps
new ConcreteClass()in one place.Javaimport java.util.List; public class Main { public static void main(String[] args) { for (String channel : List.of("email", "sms")) { System.out.println(NotificationFactory.create(channel).send("OTP 4821")); } } } interface Notification { String send(String message); } class EmailNotification implements Notification { public String send(String message) { return "email: " + message; } } class SmsNotification implements Notification { public String send(String message) { return "sms: " + message; } } class NotificationFactory { static Notification create(String channel) { return switch (channel) { case "email" -> new EmailNotification(); case "sms" -> new SmsNotification(); default -> throw new IllegalArgumentException("unknown channel " + channel); }; } }It prints
email: OTP 4821andsms: OTP 4821.List.ofandInteger.valueOfare static factories in the JDK.What is the strategy pattern?
The strategy pattern puts interchangeable algorithms behind one interface, so the caller picks one at runtime instead of branching with
if/else. With lambdas a strategy is one line:Javaimport java.util.LinkedHashMap; import java.util.Map; import java.util.function.DoubleUnaryOperator; public class Main { public static void main(String[] args) { Map<String, DoubleUnaryOperator> shipping = new LinkedHashMap<>(); shipping.put("standard", kg -> 40 + 10 * kg); shipping.put("express", kg -> 100 + 25 * kg); shipping.put("pickup", kg -> 0); for (var entry : shipping.entrySet()) { System.out.println(entry.getKey() + " " + entry.getValue().applyAsDouble(2)); } } }It prints
standard 60.0,express 150.0andpickup 0.0.Comparatoris the strategy everyone uses:list.sort(byName)versuslist.sort(byAge).What is the observer pattern?
A subject keeps a list of listeners and notifies all of them when something happens, without knowing what they do.
Javaimport java.util.ArrayList; import java.util.List; import java.util.function.Consumer; public class Main { public static void main(String[] args) { OrderEvents events = new OrderEvents(); events.subscribe(id -> System.out.println("email sent for " + id)); events.subscribe(id -> System.out.println("stock updated for " + id)); events.publish("A17"); } } class OrderEvents { private final List<Consumer<String>> listeners = new ArrayList<>(); void subscribe(Consumer<String> listener) { listeners.add(listener); } void publish(String orderId) { for (Consumer<String> listener : listeners) listener.accept(orderId); } }It prints
email sent for A17thenstock updated for A17. Pitfall: listeners that are never removed leak memory.What is the builder pattern, and when do you use it?
A builder sets an object's values through named calls and creates it in one
build(). Use it when a class has many optional parameters, instead of telescoping constructors such asnew User("Asha", null, null, 0, true).Javapublic class Main { public static void main(String[] args) { UserProfile p = new UserProfile.Builder("Asha") .email("asha@example.com") .city("Pune") .build(); System.out.println(p); } } class UserProfile { private final String name; private final String email; private final String city; private UserProfile(Builder b) { name = b.name; email = b.email; city = b.city; } @Override public String toString() { return name + " <" + email + "> " + city; } static class Builder { private final String name; private String email = "none"; private String city = "unknown"; Builder(String name) { this.name = name; } Builder email(String email) { this.email = email; return this; } Builder city(String city) { this.city = city; return this; } UserProfile build() { return new UserProfile(this); } } }It prints
Asha <asha@example.com> Pune.build()is the one place to validate, and the result can be immutable.
SOLID Principles and Design Patterns Interview Questions
The five SOLID principles, composition over inheritance, and the common patterns: singleton, factory, strategy, observer, builder.