Skip to content

Classes and Objects — Interview

A 50+ question Q&A bank covering the topic across all skill levels. Every question has the kind of answer an interviewer is actually listening for, plus the follow-up the strong candidate would volunteer.


Junior (1–15)

Q1. What is the difference between a class and an object?

A class is a blueprint — a compile-time declaration that defines fields and methods. An object is a runtime instance of that class — a concrete chunk of memory on the heap whose layout is described by the class. There is exactly one Class<Foo> metadata object per loaded class, but you can have any number of Foo instances.

Q2. What does new do, step by step?

  1. Allocates memory on the heap large enough for the object (via TLAB bump-pointer in the common case).
  2. Writes the object header (mark word + klass pointer).
  3. Zeroes all fields to their defaults.
  4. Invokes <init> — the constructor — which runs your code.
  5. Returns a reference to the new object.

Q3. Why does Dog d; not allocate an object?

That's a reference declaration, not an instantiation. d is a local variable of type Dog initialized to null. The actual Dog object only exists after d = new Dog().

Q4. What is a constructor and how does it differ from a method?

A constructor is a special block that runs once when an object is created. It has the same name as the class, has no return type (not even void), and is invoked via new (or this(...) / super(...) chaining). Unlike methods, you cannot call it directly on an existing object.

Q5. What is the default constructor?

If a class declares no constructor, the compiler generates a public no-arg constructor that calls super(). As soon as you declare any constructor, that freebie disappears.

Q6. What happens if you compare two objects with ==?

== compares references (memory addresses for reference types). Two different objects with identical content compare unequal. Use .equals() for content equality.

Q7. Why does String s1 = "x"; String s2 = "x"; s1 == s2 return true?

Because of the string pool (string interning). String literals are deduplicated by the JVM, so both variables point to the same object. new String("x") == "x" returns false.

Q8. What is this?

A reference to the current object inside an instance method or constructor. Every non-static method has a hidden first parameter equivalent to this. You use it to disambiguate field vs parameter names, pass the current object to other code, or chain constructors.

Q9. What is a field's default value?

  • Numeric primitives: 0 (or 0L, 0.0).
  • boolean: false.
  • char: ''.
  • Reference types: null.

Local variables get no default — using one before assignment is a compile error.

Q10. What is the difference between a field and a local variable?

A field is per-object state stored on the heap inside the object; a local variable lives on the call stack and disappears when the method returns. Fields get default values; local variables must be definitely assigned before use.

Q11. How do you make a class immutable?

  • Mark the class final (or use a sealed hierarchy of immutable subclasses).
  • Make every field final and private.
  • Don't expose setters; only return new instances on "modifying" operations.
  • Defensive-copy any mutable parameters into the class and any mutable fields out of it.
  • Don't let this escape during construction.

Q12. What's the difference between == and .equals() for Integer?

== compares references. Integer caches values from -128 to 127, so Integer a = 100; Integer b = 100; a == b is true, but Integer a = 200; Integer b = 200; a == b is false. Always use .equals() (or unbox to int) for value comparison.

Q13. Can a Java file have more than one class?

Yes — any number of package-private top-level classes. But at most one of them can be public, and its name must match the file name.

Q14. What is method overloading?

Multiple methods in the same class with the same name but different parameter lists (different counts, types, or order). Resolved at compile time by the compiler based on argument types — not by the runtime types.

Q15. What is method overriding?

A subclass method with the same signature as a superclass method replaces the superclass implementation. Resolved at runtime via virtual dispatch — invokevirtual looks up the actual method in the receiver's vtable.


Middle (16–30)

Q16. Why must equals and hashCode be overridden together?

Because the contract states: equal objects must have equal hash codes. If you override equals but not hashCode, two equal objects can have different hashes, and HashMap/HashSet lookups fail silently — map.put(a, ...) followed by map.get(b) (where a.equals(b)) returns null.

Q17. Walk through the equals contract.

  1. Reflexive: x.equals(x) is true.
  2. Symmetric: a.equals(b) ⇔ b.equals(a).
  3. Transitive: a.equals(b) and b.equals(c)a.equals(c).
  4. Consistent: repeated calls give the same answer if no relevant state changed.
  5. x.equals(null) is false.

A common breaker is symmetry — subclass.equals(superclass) returning true while the superclass returns false.

Q18. When would you prefer a static factory method over a constructor?

  • When you want a meaningful name (Money.usd(100) vs new Money(100, "USD")).
  • When you want to cache instances (Boolean.valueOf(true) always returns the same object).
  • When you want to return a subtype (Collections.unmodifiableList returns a hidden private subclass).
  • When construction has multiple "modes" you want to expose by name (e.g., Duration.ofSeconds, Duration.ofMinutes).

Q19. What is the difference between an entity and a value object?

Identity. An entity has an identifier and is the same entity over time even as fields change (User#id). A value object is defined by its fields; two values with the same fields are the same value (Money(100, "USD")).

Q20. Why shouldn't a constructor call overridable methods?

Subclasses' overrides will run before the subclass's own constructor has initialized its fields. The override observes a half-built object — fields at default values, invariants not yet established. Mark constructor helpers private or final.

Q21. What is the difference between shallow and deep copy?

A shallow copy duplicates the top-level object but reuses the same reference fields. A deep copy duplicates the entire object graph reachable through references. clone() defaults to shallow; deep cloning usually requires custom code.

Q22. What does final mean on (a) a class, (b) a field, (c) a method, (d) a parameter?

  • final class: cannot be subclassed.
  • final field: must be assigned exactly once (in declaration, instance/static initializer, or constructor) and cannot be reassigned. Crucially, final reference fields enable the JMM safe-publication guarantee.
  • final method: cannot be overridden.
  • final parameter: cannot be reassigned in the method body. Required for use in lambdas/anonymous classes (the parameter must be effectively final).

Q23. How do you safely return a mutable internal collection?

Don't — return an immutable view or a copy. Either List.copyOf(internal) (Java 10+) or Collections.unmodifiableList(internal) (a live read-only view). Never hand out the live mutable reference.

Q24. What is the difference between Optional as a return type and as a field?

Returning Optional from a method documents that the result may be absent and forces the caller to handle that. Storing Optional as a field is generally an anti-pattern — Optional is not Serializable, adds an extra allocation per object, and tools like JPA do not handle it cleanly. Use null + @Nullable, or model absence with a sentinel object instead.

Q25. What's wrong with public mutable fields?

They bypass any invariant the class might enforce. Anyone can put the object into an illegal state, you can never add validation later without breaking callers, and you can't change the underlying representation without a binary-incompatible refactor.

Q26. What is the difference between throw and throws?

throw is a statement that raises an exception at runtime. throws is a method clause declaring that the method may propagate certain checked exceptions to its caller. The compiler enforces throws declarations for checked exceptions but not for RuntimeException subclasses.

Q27. Why is the no-arg constructor required for many frameworks (JPA, Jackson)?

Frameworks construct objects via reflection and need a way to instantiate the class without knowing your domain-specific arguments. Modern alternatives — Jackson's @JsonCreator, JPA AttributeConverters, MapStruct — let you avoid that requirement. With Java records, frameworks supporting the canonical record constructor sidestep it as well.

Q28. What does instanceof test?

Whether the runtime type of a reference is the given class or a subclass/implementer of it. Returns false for null. Java 16+ pattern matching combines the test with binding: if (o instanceof Money m) { use(m); }.

Q29. What are the differences between Class.forName("X"), X.class, and obj.getClass()?

  • X.class (class literal): pure compile-time, never triggers initialization.
  • Class.forName("X"): runtime lookup and initialization (unless you pass false for the initialize flag).
  • obj.getClass(): returns the runtime class of obj, never null.

Q30. What's the difference between .equals() and Comparable.compareTo()?

equals returns boolean and answers "are these the same?" compareTo returns an int and answers "what is their order?" By convention they should be consistent with equals: compareTo returning 0 should imply equals returning true. BigDecimal famously violates this — new BigDecimal("1.0").equals(new BigDecimal("1.00")) is false, but compareTo returns 0. Don't put BigDecimal in TreeSet and expect HashSet semantics.


Senior (31–42)

Q31. How do you choose between a class hierarchy and composition?

Use inheritance only when the subclass genuinely is a kind of the superclass and the superclass was designed for extension (Reader, AbstractList). Use composition when: - You only need to reuse behavior, not be substitutable. - The base class wasn't designed to be subclassed (no protected extension points, no documentation of self-use patterns). - You need to vary multiple axes (composition gives you orthogonal axes; inheritance forces a single linear chain).

Q32. What is the diamond problem and how does Java handle it?

Multiple supertypes provide the same method signature. Java disallows multiple inheritance of state (one superclass max), but allows multiple inherited interfaces. When two interfaces define a default method with the same signature, the implementing class must explicitly resolve it via Iface1.super.method() or override it. The compiler will not silently pick one.

Q33. What is a sealed class and when is it useful?

Java 17+ feature. sealed class X permits A, B, C declares that only A, B, C may extend X. Combined with switch pattern matching, the compiler enforces exhaustiveness. Useful for closed sets — state machines, AST nodes, result types — where you want to add new variants intentionally and have the compiler list every place that needs an update.

Q34. Walk through the JMM guarantee for final fields.

The JMM says: if an object's constructor doesn't let this escape, then any thread that obtains a reference to the object after the constructor finishes is guaranteed to see all final fields fully initialized — without any explicit synchronization. This is the foundation of safe immutable publication. Non-final fields don't get this guarantee, so unsafe publication can cause readers to see default values for them.

Q35. What is the difference between an inner class and a static nested class?

  • Static nested class: a regular class scoped inside another. No implicit reference to the outer instance. Use it as the default for nested types.
  • Inner class (non-static): each instance holds an implicit reference to an enclosing outer-class instance. Cannot be instantiated without an outer instance. Watch for memory leaks — the inner instance keeps the outer alive even when the outer is no longer logically needed.

Q36. How would you design a class for thread safety?

First, ask: do I need it at all? Confinement to a single thread or making the class immutable removes the question. If shared mutable state is required: - Pick one synchronization strategy (intrinsic lock vs ReentrantLock vs lock-free atomics) and stick to it. - Document with @GuardedBy or comments which lock protects which fields. - Don't expose internal locks publicly — wrap private locks. - Don't call out (callbacks, listeners) while holding a lock. - Use java.util.concurrent collections rather than synchronized(map).

Q37. What is "leaking this" and why does it matter?

Letting a reference to the partially constructed object escape (via registering with a listener, starting a thread that uses this, calling overridable methods that pass this). Other code may then observe fields at default values, leading to NPEs and broken invariants. Fix: do all that after the constructor returns, in an init() method or a static factory.

Q38. When would you make a class final?

  • Value objects, where subclassing breaks the symmetry of equals.
  • Classes you don't intend to support as part of an extension contract — keeping them final lets you refactor freely.
  • Security-sensitive classes, where a malicious subclass could undermine invariants (String, Integer are final for this reason).
  • Performance-sensitive classes, where the JIT may inline more aggressively without CHA dependencies (a small but real win).

Q39. What's the impact of generics on class design?

Java generics are erased — at runtime there's just one List class. Consequences: - You cannot new T() (no class metadata at runtime). - You cannot create T[] without unchecked casts. - Bridge methods get generated for inheritance chains crossing erasure boundaries — visible in javap. - Variance is explicit at use site: List<? extends T> for producers, List<? super T> for consumers (PECS).

Q40. What's the cost of using reflection?

  • First call is significantly slower (lookup, security checks).
  • After ~15 invocations HotSpot generates a bytecode adapter, narrowing the gap to ~2–3× a direct call.
  • MethodHandle is essentially as fast as a direct call once compiled.
  • Reflection bypasses static analysis (refactoring tools, the verifier in some sense, dependency injection frameworks all build on it knowing this trade-off).

Q41. How does CHA affect virtual call performance?

Class Hierarchy Analysis: HotSpot tracks how many implementations of a virtual method are loaded. With one impl, it speculatively inlines the call as if it were direct, installing a dependency. If a new subclass loads, the compiled code is invalidated. With two or three impls, an inline cache is used. Beyond that (megamorphic), it falls back to vtable lookup — ~2× slower than the inlined case but still very fast.

Q42. Why is Object.hashCode() not free?

Default Object.hashCode() returns the identity hash, computed lazily and stored in the mark word the first time you ask for it. The first call may CAS the mark word; if the object is locked, the hash is "displaced" to the lock record. So calling hashCode on a freshly allocated object is cheap, but it interacts subtly with locking — and it's why locking + System.identityHashCode together can be surprisingly slow in tight loops.


Professional (43–52)

Q43. How does HotSpot lay out object fields, and why?

Largest first (longs/doubles → ints → shorts → bytes → references), to minimize padding. Header occupies the first 12 or 16 bytes. The trailing pad ensures the object size is a multiple of 8. Inspect with JOL. Manual reordering rarely beats the default, but it can help when you want to align hot fields onto the same cache line or apart from cold fields (@Contended for false-sharing avoidance).

Q44. What is the difference between TLAB and eden?

TLAB (Thread-Local Allocation Buffer) is a private slice of eden space owned by a single thread. Allocation in TLAB is bump-pointer, no synchronization. When the TLAB fills up, the thread asks for a new slice from eden. Eden itself is shared — direct allocations there require atomic operations (CAS on the bump pointer). TLAB exists to make most allocations contention-free.

Q45. How does scalar replacement work?

The JIT performs escape analysis to prove a freshly allocated object never leaves its method. If it doesn't escape, the JIT replaces the object with its individual fields stored in registers/stack slots — the allocation is elided entirely. You can observe this with -XX:+PrintEliminateAllocations. This is why short-lived helper objects (Pair, Optional in many cases, small records) often have zero runtime cost.

Q46. What does -XX:+UseCompressedOops do?

On 64-bit JVMs with heaps under ~32 GB, references can be stored as 32-bit values (offsets from the heap base, scaled by 8) instead of 64-bit pointers. Halves the size of every reference field and roughly halves typical Java heap usage. Above ~32 GB the savings are lost (though -XX:+UseCompressedClassPointers independently keeps the klass pointer compressed). The default is on for heaps ≤ ~32 GB.

Q47. What happens during class verification?

The JVM bytecode verifier proves several invariants: the operand stack always has the right depth and types at each instruction, locals are typed correctly, branches don't escape the method, the object is fully initialized before being used (constructor must invoke super() first), and no instruction can violate JVM safety. Fails throw VerifyError. Modern JVMs use type-checking verification (since class file v50, Java 6) which uses StackMapTable attributes for fast verification.

Q48. What is class data sharing and why does it speed up startup?

CDS / AppCDS dumps loaded class metadata to a shared archive file (classes.jsa). On startup the JVM mmaps this file and skips loading and verifying those classes. Tools like Spring Boot's -XX:ArchiveClassesAtExit=app.jsa followed by -XX:SharedArchiveFile=app.jsa can cut cold-start time by 50%+ for large applications.

Q49. What is JEP 401 (value classes) and how will it change OOP?

Value classes (Project Valhalla) are classes without identity — == becomes field equality, synchronized(c) is forbidden, and the runtime can store instances inline (no header, no pointer). A value class Point { int x, y; } becomes effectively zero-overhead vs a primitive struct. Once stable, the line between "primitive" and "object" blurs: BigInteger could be a value class, generic collections could specialize, and many today's escape-analysis-dependent optimizations become unnecessary.

Q50. Why does the JMM not guarantee visibility for non-final fields without synchronization?

Because the JMM is intentionally a weak memory model that allows compilers and CPUs to reorder reads and writes for performance. Without explicit synchronization (volatile, locks, Atomic*, final field semantics), a write by one thread may be cached, reordered, or simply not propagated to another thread for an indefinite time. The fix is safe publication: store the reference in a synchronized location (volatile field, AtomicReference, immutable container, synchronized block).

Q51. What's the cost of synchronized on an uncontended lock?

On modern HotSpot: a single CAS on the mark word for lightweight locking (~10 ns). Under no contention, it's nearly free. Under contention, the lock inflates to a ObjectMonitor (a C++-allocated structure), and the next acquire involves OS-level park/unpark — orders of magnitude more expensive. JEP 374 deprecated biased locking, which used to be even cheaper for the single-thread case but added complexity.

Q52. What are nest mates and why were they introduced?

Pre-JDK 11, when an inner class accessed a private field of its outer class, javac generated a synthetic package-private bridge method (access$000). JEP 181 introduced nest mates — classes that share NestHost/NestMembers attributes can directly access each other's private members at the JVM level. Cleaner bytecode, smaller class files, and better reflection (Class.getNestHost).


Behavioral / Design Round (bonus)

  • "Tell me about a time you had to refactor a large class." — frame around: identifying multiple responsibilities, picking a refactoring (extract class, move method), preserving behavior with characterization tests, rolling out incrementally.
  • "How do you decide between a record and a class?" — record when: small immutable carrier, you want equals/hashCode/toString for free, you don't need a no-arg constructor for frameworks, no inheritance. Class when: hidden representation, mutable state, custom equality, or extension is part of the contract.
  • "How do you handle adding a feature to a class with no tests?" — Michael Feathers' approach: add characterization tests that pin down current behavior, then refactor under the safety net.

The pattern across all of these: a strong answer is specific, gives examples, names trade-offs explicitly, and acknowledges what the listener can verify. Generic platitudes ("encapsulation is good") are filler. Concrete observations ("I make values final because subclassing breaks equals symmetry — I'll show you") are signal.