Constructors and overloading, private constructors, anonymous classes, the Singleton pattern and making it thread-safe, and how to build a truly immutable class.
Published September 25, 2026
The singleton and immutable-class questions are favourites because they have a "textbook" answer and a correct answer. Most candidates give the textbook one: a lazy singleton that isn't thread-safe, or an immutable class that still leaks a mutable List. Give the correct one.
Short answer: A constructor is a special block that initialises a new object. It has the same name as the class, no return type (not even void), and runs automatically when you use new.
public class Order {
private final String id;
private final Instant createdAt;
public Order(String id) { // constructor
this.id = Objects.requireNonNull(id);
this.createdAt = Instant.now();
}
}
Key points to cover:
static, final or abstract.Learn it in depth → Classes and Objects
Short answer: Yes. A class can have several constructors with different parameter lists, and they can call each other with this(...). This is called constructor chaining, and it keeps the initialisation logic in one place.
public Order(String id) { this(id, Instant.now()); }
public Order(String id, Instant created) { this.id = id; this.createdAt = created; }
Key points to cover:
Short answer: Yes. A private constructor stops other classes from calling new. The typical uses are:
Collections and Math).List.of, Optional.of).public final class StringUtils {
private StringUtils() { throw new AssertionError("no instances"); }
public static boolean isBlank(String s) { return s == null || s.isBlank(); }
}
Short answer: An anonymous class is a class without a name that is declared and instantiated in a single expression. It's typically used to implement an interface or extend a class for one-off use.
Comparator<String> byLength = new Comparator<>() { // anonymous class
@Override public int compare(String a, String b) { return Integer.compare(a.length(), b.length()); }
};
Comparator<String> byLength2 = Comparator.comparingInt(String::length); // the modern equivalent
Key points to cover:
this inside it refers to the anonymous object, whereas this inside a lambda refers to the enclosing object.Short answer: A class that allows exactly one instance in the application (strictly, per class loader), and provides a global access point to it. Examples include a configuration registry, a metrics registry, or an in-memory cache.
Key points to cover:
getInstance().Learn it in depth → Singleton Pattern
Short answer:
private.private static field.public static accessor.The simplest correct versions are eager initialisation, or an enum:
// 1) Eager: created when the class is initialised; thread-safe thanks to class-loading guarantees
public final class AppConfig {
private static final AppConfig INSTANCE = new AppConfig();
private AppConfig() { }
public static AppConfig getInstance() { return INSTANCE; }
}
// 2) Enum: also safe against reflection and serialization attacks (recommended in Effective Java)
public enum MetricsRegistry {
INSTANCE;
public void increment(String name) { /* … */ }
}
Learn it in depth → Singleton Pattern
Short answer: It depends on how it's created. A lazy singleton written like this is not thread-safe, because two threads can both see null and each create an instance:
public static Cache getInstance() {
if (instance == null) instance = new Cache(); // race condition
return instance;
}
Thread-safe options:
volatile field.synchronized accessor: simple, but every call takes the lock.public final class Cache {
private Cache() { }
private static class Holder { static final Cache INSTANCE = new Cache(); } // loaded on first use
public static Cache getInstance() { return Holder.INSTANCE; }
}
Key points to cover:
volatile. Without it, another thread can see a reference to a partly constructed object.Short answer: An immutable object's state can't change after construction. Any "modification" returns a new object instead. Examples: String, Integer, LocalDate, BigDecimal, and records with immutable fields.
LocalDate d = LocalDate.of(2026, 1, 31);
d.plusDays(1); // returns a NEW date; d is unchanged
d = d.plusDays(1); // you must use the returned value
Short answer: Their state never changes, so any number of threads can read them at the same time with no locking and no risk of seeing a half-updated object. They're thread-safe by construction.
Key points to cover:
final fields, the Java Memory Model guarantees that other threads see the fully constructed values once the object is published, provided this doesn't escape during construction.HashMap keys (the hash never changes), are easy to cache and share, and are simple to reason about.Learn it in depth → Volatile and the Java Memory Model
Short answer: Classes whose instances can't be modified after creation. All state is set once, usually in the constructor. There are no setters, and no method leaks a way to change the internals.
Short answer:
final, so no subclass can add mutability.private final.public final class Invoice {
private final String number;
private final List<String> lineItems;
public Invoice(String number, List<String> lineItems) {
this.number = number;
this.lineItems = List.copyOf(lineItems); // copy in: the caller's list can't change us later
}
public String number() { return number; }
public List<String> lineItems() { return lineItems; } // List.copyOf is already unmodifiable
}
Common trap: stopping after rule 4. final List<String> items only stops the reference from changing. The list itself can still be modified through the caller's reference, or through a getter that returns it. Rule 5 is the one interviewers look for.
Key points to cover:
List.copyOf in a compact constructor to finish the job:public record Invoice(String number, List<String> lineItems) {
public Invoice { lineItems = List.copyOf(lineItems); }
}
Learn it in depth → Records
Q: Can a singleton be broken? A: Yes, in three ways:
readResolve() to return INSTANCE.Cloneable.An enum singleton is immune to all three.
Q: Is a singleton unique across the whole JVM? A: Only per class loader. App servers and plugin systems that load the same class through two class loaders get two "singletons".
Q: What's the difference between an immutable object and a final variable?
A: A final variable can't be reassigned to point at another object, but the object it points to can still change (final List → add() works). Immutability is a property of the object.
Q: Why is String immutable?
A: So that strings can be safely shared in the string pool, cache their hash codes, be used as secure keys (file paths, class names, URLs) without being altered after validation, and be shared between threads freely.