Question 11: The "E-Commerce Discount" Scenario (Polymorphism & Open-Closed Principle)
You are building a checkout system. Different customers get different discounts: StudentDiscount (10%), VIPDiscount (20%), and SeasonalDiscount (variable).
Your lead developer says: 'I don't want to see a single if-else or switch statement in the calculateTotal() method when checking for the discount type.'
How would you use Polymorphism to design this so that adding a new discount type in the future (like BlackFridayDiscount) requires zero changes to the existing checkout logic?
I will create an interface called Discount, which will have the abstract method calculateTotal() and each classes such as StudentDiscount, VIPDiscount and SeasonalDiscount etc will have its own implementation for calculating total, depending on the type of object doing checked out the right class which implementing the Discount interface will get called.
That is exactly the right approach. By shifting the logic from a central "checker" into the objects themselves, you are following the Strategy Design Pattern.
Interviewer Feedback & Correct Answer
The Strategy Pattern: You nailed it. By using the
Discountinterface, theCheckoutclass only needs to calldiscount.calculateTotal(). It doesn't care if it's a student, a VIP, or a seasonal promotion.Open-Closed Principle: This is a perfect example of being Open for extension (you can add
BlackFridayDiscountby creating a new class) but Closed for modification (you never have to touch theCheckoutclass code again).Pro-Tip: In a real interview, you might mention that these discount objects could be passed into the Checkout service via Dependency Injection, making the system even more decoupled.
Question 12: The "Circular Reference" Scenario (Memory & Relationships)
Imagine you have two classes: Employee and Department.
An
Employeehas a reference to theirDepartment.A
Departmenthas aList<Employee>of everyone working there.
This is a Bidirectional Relationship. From an OOP design perspective, how do you ensure that when an Employee moves to a new Department, both references are updated correctly to prevent 'ghost' data?
Does this circular reference cause a Memory Leak in Java's Garbage Collection process (like it might in older languages like C++)? Why or why not?
Interviewer Feedback & Correct Answer
Manual Synchronization: In plain Java (which JPA does behind the scenes), you must implement "Defensive Link Management." Typically, you would modify the
setDepartmentmethod in theEmployeeclass to automatically remove the employee from the old department's list and add them to the new one. This maintains Referential Integrity.Memory Management: You'll be glad to know that circular references do not cause memory leaks in Java.
Why? Unlike older systems that used "Reference Counting," the JVM uses "Reachability Analysis" (Mark-and-Sweep).
The Logic: Even if
EmployeeandDepartmentpoint to each other, if they are no longer reachable from a GC Root (like a running Thread or a Static variable), the Garbage Collector is smart enough to see them as an isolated "island" and reclaim them both.
Question 13: The "Deep vs. Shallow Copy" Scenario (Cloning Objects)
You are working on a Graphic Design App. You have a Document class that contains a List<Shape> and a Metadata object.
If you implement a 'Duplicate Document' feature using a Shallow Copy, what happens to the shapes in the new document when a user modifies a shape in the original document?
How would you implement a Deep Copy to ensure that the duplicated document is entirely independent, and what are the common pitfalls (like performance or circular references) when doing this manually?
Interviewer Feedback & Correct Answer
Shallow Copy: In your scenario, a shallow copy creates a new
Documentobject, but it copies the reference to the existingList<Shape>.The Result: Both the "Original" and the "Duplicate" documents point to the exact same list in memory. If a user moves a circle in Document A, it moves in Document B too. This is usually a bug in a "Duplicate" feature.
Deep Copy: To implement this, you must iterate through the
List<Shape>, create a new instance of every singleShape, and add those to a brand-newList.Common Pitfalls:
Performance: Deep copying very large object graphs (thousands of shapes) can be slow and memory-intensive.
Circular References: If Shape A points to Shape B, and Shape B points back to Shape A, a simple recursive deep copy will trigger a
StackOverflowError.
Question 14: The "Immutable User Profile" (Design Patterns & Encapsulation)
In a distributed system, you want to ensure that once a UserSession object is created, it cannot be changed by any thread (making it thread-safe without using synchronization).
What are the four main steps you must take to make a Java class truly Immutable?
How does the Builder Pattern complement an Immutable class when that class has 15+ optional fields?
Interviewer Feedback & Correct Answer
Refining Step 3 (The Getter Rule): You mentioned "setters," but a truly immutable class should not have any setters at all. For the getters, you are exactly right—if you have a mutable field (like a
Dateor aList), you must return a defensive copy so the caller can't modify the internal state of your object.The 4 Steps Summary:
Mark the class as
final.Make all fields
privateandfinal.No setter methods.
Initialize all fields via a constructor (performing deep copies of mutable inputs) and return defensive copies in getters.
The Builder Pattern: Spot on. When you have 15+ fields, a constructor with 15 arguments is a nightmare to read (
new User("John", null, null, 25, true...)). The Builder allows you to set only what you need:User.builder().name("John").age(25).build(). This keeps the finalUserobject immutable while making the creation process readable and flexible.
You have an interface SmartPaymentProvider with methods: payByCreditCard(), payByCrypto(), and payByBankTransfer().
Now, you want to onboard a small vendor who only accepts Credit Cards. If they implement your interface, they are forced to provide 'dummy' or 'empty' implementations for Crypto and Bank Transfers, which is a bad design.
Which SOLID principle are we violating here?
How would you refactor these interfaces to support different vendors with different capabilities without forcing them to implement methods they don't need?
Interviewer Feedback & Correct Answer
ISP Violation: You're spot on. Forcing a class to implement methods it doesn't use creates "fat" interfaces and leads to
UnsupportedOperationExceptionrisks.Refactoring Strategy: While your
makePayment()idea is a good general abstraction, to truly follow ISP, the standard approach is to split the one large interface into several smaller, specialized ones:CreditCardPaymentProvider{payByCreditCard()}CryptoPaymentProvider{payByCrypto()}BankTransferProvider{payByBankTransfer()}
The Benefit: Now, your small vendor only implements
CreditCardPaymentProvider. A larger vendor like PayPal could implement all three. This keeps the code clean and the dependencies minimal.
Question 16: The "Database Connection" (Static Blocks vs. Constructors)
You are writing a class DatabaseConfig. You have some heavy configuration data that needs to be loaded from a file exactly once when the class is first loaded into memory, regardless of how many objects of that class are created.
Would you put this loading logic in a Static Initializer Block or the Constructor? Why?
If the loading logic throws an exception (e.g.,
FileNotFoundException), how does the JVM handle a failure inside astaticblock compared to a failure inside a constructor?
Interviewer Feedback & Correct Answer
Static Initializer Block: Correct. This is the right place for "one-time" setup. Logic in a Constructor runs every time a new object is created, whereas a Static Block runs once when the class is first loaded by the ClassLoader.
Exception Handling (The "Deadly" Part):
Constructor: If a constructor fails, you just get an exception, and that specific object isn't created.
Static Block: If an unchecked exception occurs here, the JVM throws a
ExceptionInInitializerError.The Consequence: Once a class fails to initialize, the JVM marks it as "unusable." Any further attempt to use the class during the application's lifecycle will result in a
NoClassDefFoundError. You can't just "try again" without restarting the JVM or reloading the class.
Question 17: The "Method Hiding" Scenario (Static Methods & Polymorphism)
Suppose you have a parent class Parent with a static method display(), and a child class Child that also has a static method display() with the exact same signature.
Parent obj = new Child();
obj.display();
Which version of
display()will be called? The one inParentor the one inChild?Is this considered Method Overriding? Why or why not?
The Result: Actually,
Parent.display()will be called.The Reasoning: Even though the object is a
new Child(), the reference variableobjis of typeParent. Sincestaticmethods are resolved at compile-time based on the reference type (not the actual object on the heap), the compiler looks atParentand links it there.Method Hiding: You nailed this! It is called Method Hiding, not Overriding. Because static methods do not use dynamic method dispatch (the mechanism that enables polymorphism), they don't participate in the "runtime lookup" that makes overriding work.
Question 18: The "Abstract Constructor" (Logic vs. Syntax)
Here is a conceptual question:
Can an Abstract Class have a Constructor?
If you cannot instantiate an abstract class (
new MyAbstractClass()is illegal), what is the purpose of having a constructor inside it? How and when does it get executed?
Interviewer Feedback & Correct Answer
The Purpose: Even though you can't create an instance of an
AbstractClassdirectly, that class still defines the state (fields) that its children will inherit. The constructor ensures that the "base" part of the object is in a valid state before the child’s constructor even starts.The Execution: When you call
new Child(), the first line of the child constructor is implicitly (or explicitly)super(). This chains up to the abstract parent.Real-World Use: Imagine an abstract class
BaseServicethat requires aDatabaseConnection. You can force all subclasses to provide that connection by defining a constructor in the abstract class:public BaseService(DatabaseConnection conn) { ... }.
Question 19: The "Diamond Problem" (Default Methods in Interfaces)
Before Java 8, multiple inheritance of state was forbidden to avoid the 'Diamond Problem.' However, with the introduction of Default Methods in interfaces, we now have a form of multiple inheritance of behavior.
If a class
MyServiceimplements two interfaces,InterfaceAandInterfaceB, and both have a default method calledlogMessage(), what happens when you try to compile the code?How does Java force you to resolve this conflict?
Interviewer Feedback & Correct Answer
The Conflict: If
InterfaceAandInterfaceBboth have adefault logMessage()method, the compiler is paralyzed. It doesn't know which one you want to use. Unlike C++, Java refuses to "guess" or pick one based on order. It will throw an error: "Duplicate default methods named logMessage... inherit from types InterfaceA and InterfaceB."The Resolution: Java forces you to override the method in your class. You must explicitly tell the compiler how to handle the overlap. You can either write your own logic or call a specific interface's version using this unique syntax:
@Override public void logMessage() { InterfaceA.super.logMessage(); // Explicitly choosing A }
Question 20: The "Object Class Contract" (equals() and hashCode())
You are using a HashMap to store Employee objects as keys.
If you override the
equals()method but forget to override thehashCode()method in yourEmployeeclass, what happens when you try to retrieve an employee from the Map using a 'theoretically equal' object?What is the general contract between these two methods? If
obj1.equals(obj2)is true, what must be true about their hash codes?
Interviewer Feedback & Correct Answer
The Bucket Disaster: You're exactly right. If
obj1.equals(obj2)is true but they have different hash codes, theHashMapwill look in the wrong bucket when you try to retrieve the value. Even though the object is "equal," the Map thinks it's missing. This leads to duplicate keys in your Map (which shouldn't happen) and "lost" data.The Contract: The rule is absolute: If two objects are equal according to
equals(), they MUST have the samehashCode().Note: The reverse is not necessarily true. Two different objects can have the same hash code (this is called a Collision), but it hurts performance.
Performance Impact: If your
hashCode()method is poorly written and returns the same constant (likereturn 1;), theHashMapdegrades into aLinkedList, turning $O(1)$ lookups into $O(n)$.
Question 21: The "Fragile Base Class" (Composition vs. Inheritance Revisited)
There is a famous saying in OOP: 'Favor Composition over Inheritance.'
Imagine you inherit from a
LibraryScannerclass to add a 'logging' feature. A year later, the library owners updateLibraryScannerand add a new method that your child class already has, but with a different return type. What happens to your code?How does Composition (holding an instance of
LibraryScanneras a field) protect you from these 'Side Effects' of parent class changes?
Interviewer Feedback & Correct Answer
The Conflict: If you have a method
public void process()in your child class, and the Library owners later addpublic int process()to the parent class, your code will not compile. In Java, you cannot have two methods with the same name and parameters but different return types in the same class hierarchy.Composition as the Savior: With composition, you don't "inherit" the parent's methods. You just call them. If the library adds a new
process()method, it doesn't affect your class because your class isn't a "type of" that library class. You have total control over your own method signatures.
Question 22: The "Nested Class" Choice (Static vs. Non-Static)
In your Java project, you have a Car class and a Wheel class. You decide to make Wheel an Inner Class (nested inside Car).
If you declare
Wheelas a Static Nested Class, can it access theprivate String engineTypefield of the outerCarclass?If you declare it as a Non-Static Inner Class, what 'hidden' reference does the
Wheelobject hold, and how does this affect memory/garbage collection?
Static Nested Class: Correct. A
staticnested class behaves like any other top-level class; it just happens to be packaged inside another. It has no access to the instance variables of theCar(likeengineType) because it doesn't "belong" to a specificCarobject.Non-Static Inner Class: You're on the right track, but here is the key "under-the-hood" detail:
The Hidden Reference: Every instance of a non-static inner class holds an implicit reference to its outer class instance.
Memory Impact: This means as long as the
Wheelobject is alive (e.g., it’s stuck in a long-running queue), the entireCarobject cannot be garbage collected, even if you aren't using the car anymore. This is a very common source of memory leaks!
Question 23: The "Checked vs. Unchecked" OOP Design
When designing a custom exception for a Banking Application, you have to choose between extending Exception (Checked) or RuntimeException (Unchecked).
If you create an
InsufficientFundsException, which one should it extend and why?From an OOP perspective, how does using a Checked Exception change the 'Contract' of the method for the developer who calls your API?
Interviewer Feedback & Correct Answer
Checked vs. Unchecked: For an
InsufficientFundsException, most senior architects recommend extendingException(Checked).The Reasoning: In a banking app, an "insufficient funds" error is not a programming bug (like a
NullPointerException); it is a contingency that is expected to happen in the business flow. By making it checked, you force the developer to write code that handles the scenario (e.g., prompting the user to transfer money or show an error message).When to use Runtime: Use
RuntimeExceptionfor things that shouldn't happen if the code is correct (likeIllegalTransactionStateException).
The Method "Contract": You are correct. When you add
throws InsufficientFundsExceptionto a method signature, that exception becomes part of the Public API Contract.It signals to the caller: "This operation might fail for this specific reason, and you are legally required to decide how to handle it before your code will even compile.
Question 24: The "Super vs This" in Constructors
In Java, we use super() and this() to call constructors.
Can you have both
super()andthis()as the first lines in the same constructor?What happens if you don't write
super()orthis()in your constructor at all? Does the compiler do anything behind the scenes?
Interviewer Feedback & Correct Answer
The "First Line" Rule: You are absolutely correct. Since both
this()andsuper()must be the first statement in a constructor, it is physically impossible to have both. This ensures that the object is initialized in a strictly linear fashion—you either delegate to another constructor in the same class or move up to the parent.The Invisible
super(): Correct. If you don't provide a call tosuper()orthis(), the compiler automatically inserts an implicitsuper()(the no-argument version).The Trap: If the parent class has a constructor with parameters (like
public Parent(String name)) but no no-argument constructor, your child class will fail to compile unless you manually callsuper("some name").
Question 25: The "Polymorphic Variable" Challenge (Object vs Reference)
Suppose you have:
List<String> names = new ArrayList<>();
Why is it considered better practice to declare the variable as the interface type (
List) rather than the concrete type (ArrayList)?If you later decide to change
ArrayListtoLinkedList, what is the impact on the rest of your code that uses thenamesvariable?
Interviewer Feedback & Correct Answer
Coding to an Interface: By using
List<String> names, you are telling the rest of your program, "I only care that this object behaves like a List; I don't care how it's implemented under the hood." This is a classic example of Abstration and Upcasting.Minimal Impact: Exactly. Because the rest of your code (loops,
add()calls,size()checks) only interacts with theListinterface methods, changingArrayListtoLinkedList(or evenCopyOnWriteArrayListfor threading) requires changing exactly one line of code.The Catch: The only time this backfires is if you were relying on a specific method that exists in
ArrayListbut not in theListinterface (likeensureCapacity()), but that is rare in high-quality OOP design.







