Menu
CoddyTech

Advanced OOPs Interview Questions

Equality contracts, immutability, records and sealed types, dispatch internals and object-oriented design rounds.

10 questions
  1. What happens if you override equals() but not hashCode() in Java?

    Java
    import java.util.HashSet;
    import java.util.Set;
    
    public class Main {
        public static void main(String[] args) {
            Set<Point> set = new HashSet<>();
            set.add(new Point(1, 2));
            set.add(new Point(1, 2));
            System.out.println(set.size() + " " + new Point(1, 2).equals(new Point(1, 2)));
        }
    }
    
    class Point {
        final int x, y;
    
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public boolean equals(Object o) {
            return o instanceof Point p && p.x == x && p.y == y;
        }
    }

    It prints 2 true. hashCode() was not overridden, so the equal points keep their identity hashes; with different hash codes HashSet never calls equals() and stores both. (A rare identity hash collision would give 1.) The contract: equal objects must have equal hash codes, and both methods must stay stable while the object is a key. Override them together, or use a record. See Java HashMap.

    Practice it: Contains Duplicate, Group Anagrams

  2. What is a record in Java, and when should you use one?

    A record (final since Java 16) is a class for immutable data: you declare the components, and the compiler generates the private final fields, constructor, accessors (x(), not getX()), equals(), hashCode() and toString().

    Java
    public class Main {
        record Point(int x, int y) {
            Point {
                if (x < 0 || y < 0) throw new IllegalArgumentException("negative");
            }
    
            double distance() { return Math.sqrt(x * x + y * y); }
        }
    
        public static void main(String[] args) {
            Point a = new Point(3, 4);
            System.out.println(a + " " + a.equals(new Point(3, 4)) + " " + a.distance());
        }
    }

    It prints Point[x=3, y=4] true 5.0. Records are only shallowly immutable: a List component can still change unless you copy it.

  3. How do you create an immutable class in Java?

    Make the class final (or its constructors private), every field private final, provide no setters (modifying methods return a new object), and copy mutable inputs and outputs so outside code never holds your internal state.

    Java
    import java.util.ArrayList;
    import java.util.List;
    
    public class Main {
        public static void main(String[] args) {
            List<String> source = new ArrayList<>(List.of("a"));
            Playlist p = new Playlist(source);
            source.add("b");
            Playlist q = p.with("c");
            System.out.println(p.songs() + " " + q.songs());
            try {
                p.songs().add("x");
            } catch (UnsupportedOperationException e) {
                System.out.println("read-only");
            }
        }
    }
    
    final class Playlist {
        private final List<String> songs;
    
        Playlist(List<String> songs) { this.songs = List.copyOf(songs); }
    
        List<String> songs() { return songs; }
    
        Playlist with(String song) {
            List<String> next = new ArrayList<>(songs);
            next.add(song);
            return new Playlist(next);
        }
    }

    It prints [a] [a, c], then read-only. Immutable objects are thread-safe without locks and safe as map keys.

  4. What is the difference between Comparable and Comparator?

    Comparable gives a class one natural order through compareTo(), written inside the class. A Comparator is a separate object for any other order.

    Java
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    
    public class Main {
        record Student(String name, int marks) implements Comparable<Student> {
            public int compareTo(Student other) { return Integer.compare(marks, other.marks); }
        }
    
        public static void main(String[] args) {
            List<Student> list = new ArrayList<>(List.of(
                    new Student("Ravi", 72), new Student("Asha", 91), new Student("Meera", 72)));
            Collections.sort(list);
            System.out.println(list.stream().map(Student::name).toList());
            list.sort(Comparator.comparingInt(Student::marks).reversed().thenComparing(Student::name));
            System.out.println(list.stream().map(Student::name).toList());
        }
    }

    It prints [Ravi, Meera, Asha] (by marks; the stable sort keeps the two 72s in order), then [Asha, Meera, Ravi]. Use Integer.compare rather than a - b, which overflows.

    Practice it: Merge Intervals, Kth Largest Element in an Array, Meeting Rooms

  5. How many adds does a counting Java HashSet subclass record for addAll of three?

    Java
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.List;
    
    public class Main {
        public static void main(String[] args) {
            CountingSet<String> set = new CountingSet<>();
            set.addAll(List.of("a", "b", "c"));
            System.out.println(set.added);
        }
    }
    
    class CountingSet<E> extends HashSet<E> {
        int added = 0;
    
        @Override
        public boolean add(E e) {
            added++;
            return super.add(e);
        }
    
        @Override
        public boolean addAll(Collection<? extends E> c) {
            added += c.size();
            return super.addAll(c);
        }
    }

    addAll adds 3 to the counter, then super.addAll (inherited from AbstractCollection) calls add() for each element. That add() is our override, so every element is counted twice, giving 6. The subclass depends on an undocumented detail of its parent: the fragile base class problem, fixed by holding a Set instead of extending one.

  6. What are sealed classes, and how do they change polymorphism in Java?

    A sealed class or interface lists exactly which classes may extend it (final since Java 17). With pattern matching in switch (final since Java 21, JEP 441), the compiler checks that a switch covers every subtype.

    Java
    public class Main {
        sealed interface Shape permits Circle, Rect {}
    
        record Circle(double r) implements Shape {}
    
        record Rect(double w, double h) implements Shape {}
    
        static double area(Shape s) {
            return switch (s) {
                case Circle c -> Math.PI * c.r() * c.r();
                case Rect r -> r.w() * r.h();
            };
        }
    
        public static void main(String[] args) {
            System.out.printf("%.2f %.2f%n", area(new Circle(1)), area(new Rect(2, 3)));
        }
    }

    It prints 3.14 6.00. There is no default; add a Triangle to permits and the switch stops compiling until it handles it.

  7. How does runtime polymorphism work internally?

    Through a method table, a vtable, in HotSpot and the major C++ compilers (neither standard requires it). Each class has a table of pointers to its overridable methods, an override takes the same slot as the method it replaces, and each object points to its class. A virtual call reads that slot and jumps.

    • Interface calls in the JVM use a separate itable.
    • HotSpot's JIT inlines a call site that has seen one class, with a guard that deoptimizes if another appears.
    • Python looks methods up by name along the MRO at runtime.
  8. What is the Law of Demeter?

    A method should call methods only on itself, its fields, its parameters and objects it creates, not on objects those calls return.

    // Violates: the caller knows Order, Customer, Address and their structure
    String city = order.getCustomer().getAddress().getCity();
    
    // Better: ask the object that owns the knowledge
    String city = order.shippingCity();
    

    The chain couples the caller to three classes. Fluent APIs such as stream().filter().map() are not violations, because each call returns the same kind of object.

  9. How would you approach an object-oriented design question such as "design a parking lot"?

    Clarify requirements, turn the nouns into classes and the verbs into methods, then walk one use case through the objects. The process and the trade-offs are graded, not the diagram.

    1. Ask: floors, vehicle and spot sizes, payment.
    2. Classes: ParkingLot, Floor, Spot, Vehicle, Ticket, PricingPolicy.
    3. Behavior: park(vehicle) returns a Ticket; unpark(ticket) frees the spot and returns the fee.
    interface SpotFinder { Optional<Spot> find(List<Floor> floors, Vehicle v); }
    interface PricingPolicy { long feeInPaise(Ticket t, Instant exit); }
    
    class ParkingLot {
        private final List<Floor> floors;
        private final SpotFinder finder;
        private final PricingPolicy pricing;
    
        Ticket park(Vehicle v) { ... }
        long unpark(Ticket t) { ... }
    }
    

    Mention concurrency (two cars must not claim one spot) and keep money in integer units, not double.

  10. In Python, what happens to __hash__ when you define __eq__?

    Defining __eq__ without __hash__ sets __hash__ to None, so instances become unhashable and cannot be set members or dict keys. That protects the rule that equal objects have equal hashes (see the __hash__ docs).

    Python
    from dataclasses import dataclass
    
    class Point:
        def __init__(self, x, y):
            self.x, self.y = x, y
    
        def __eq__(self, other):
            return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)
    
    print(Point(1, 2) == Point(1, 2))
    try:
        {Point(1, 2)}
    except TypeError as e:
        print(e)
    
    @dataclass(frozen=True)
    class FrozenPoint:
        x: int
        y: int
    
    print(len({FrozenPoint(1, 2), FrozenPoint(1, 2)}))

    It prints True, then unhashable type: 'Point', then 1. Define __hash__ from the same fields (return hash((self.x, self.y))) or use @dataclass(frozen=True), which generates both.

Coddy programming languages illustration

Learn to code with Coddy

Get started