Interview Deep Dive: Common Questions and Variations
The Setup: You need to model books, members, and borrowing. How do you handle different member types (Student, Faculty, Guest) and different book types (ReferenceBook, RegularBook, Magazine)?
First, use inheritance for Member types. Student, Faculty, and Guest are genuinely different member categories with shared attributes (memberId, name) but different borrowing limits and privileges. This passes the is-a test.
Second, use inheritance for Book types. ReferenceBook and RegularBook share core book attributes (ISBN, title, author) but have different borrowing rules (reference books cannot be borrowed).
Third, compose a BorrowingPolicy into Member. Instead of hardcoding limits, inject a policy that can be changed. A Faculty member might upgrade their policy without changing their member type.
- policy: BorrowingPolicy
+ getMaxBooks(): int
The Setup: You have a Bird class with a fly() method. How do you handle Penguin, which cannot fly?
fly() { throw new UnsupportedException() }
}
First, separate Flyable interface from Bird. Not all birds fly.
Second, birds that fly implement Flyable and compose FlyingBehavior.
Third, Penguin extends Bird but does not implement Flyable. It composes SwimmingBehavior instead.
The Setup: Design a vending machine that dispenses products, accepts payment, and maintains inventory. How do you model products and payment methods?
Product Hierarchy (Inheritance): Beverage, Snack, and Sandwich all extend Product. They share price and inventory management but have different dispensing mechanisms.
Payment Strategy (Composition): VendingMachine composes PaymentProcessor, which uses a PaymentMethod interface. This allows cash, card, and mobile payments without changing the machine's core logic.
State Pattern (Composition): VendingMachine composes VendingMachineState (Idle, AcceptingPayment, Dispensing). State changes at runtime without inheritance.
Question: What if an object needs behaviors from multiple sources? For example, an Amphibian that swims and walks.
First, most modern languages avoid multiple inheritance due to the Diamond Problem (ambiguous method resolution).
Second, use interfaces plus composition. Amphibian implements Swimmable and Walkable, and composes SwimmingBehavior and WalkingBehavior.
Third, this approach avoids inheritance ambiguity while providing multiple capabilities. Each behavior is independently testable and replaceable.
First, define base classes and hierarchies. This establishes the domain model quickly.
Second, implement core methods with hardcoded logic. Get the parking lot working with basic car parking first.
Third, refactor to composition for payment or pricing strategies if time permits. Mention to the interviewer that you are aware of Strategy pattern but prioritizing working code.
Fourth, if asked to extend the system (add a new vehicle type or payment method), this is when you showcase composition. Show how adding ElectricCar extends Vehicle (inheritance) and adding CryptocurrencyPayment implements PaymentStrategy (composition) without modifying existing code (Open-Closed Principle).
First, blindly applying "composition over inheritance" without justification. If you refuse to use inheritance even for clear hierarchies like Shape-Circle, you signal dogmatism over understanding.
Second, creating inheritance hierarchies based on implementation sharing rather than conceptual relationships. If you make CircleWithLogging extend Circle just to add logging, you misunderstand inheritance. Use a decorator or aspect instead.
Third, violating LSP by making subclasses weaker than parents. If Square extends Rectangle but breaks because setting width independently does not work, you have misused inheritance. In this case, both should implement a Shape interface.