A deep-dive through twelve chapters of Q&A — every question Glassdoor has whispered of, every pattern Xperi has asked, every nuance the JVM hides beneath the surface.
Begin Reading →Encapsulation · Inheritance · Polymorphism · Abstraction. The four cornerstones examined through real-world analogies that interviewers love.
1. Encapsulation — Data (fields) aur behavior (methods) ko ek class mein bundle karna, aur access ko control karna private/public ke zariye.
private hai. Sirf deposit() / withdraw() se access hoga. Koi bahar se seedha balance change nahi kar sakta.public class BankAccount { private double balance; // encapsulated public void deposit(double amount) { if (amount > 0) balance += amount; } public double getBalance() { return balance; } }
2. Inheritance — Ek class doosri class ke properties aur methods inherit karti hai. Code reuse hota hai.
class Animal { void breathe() { System.out.println("Breathing"); } } class Dog extends Animal { void bark() { System.out.println("Woof!"); } } // Dog can breathe() AND bark()
3. Polymorphism — Same method, different behavior. Compile-time (overloading) aur Runtime (overriding) dono hote hain.
class Shape { void draw() { System.out.println("Drawing Shape"); } } class Circle extends Shape { @Override void draw() { System.out.println("Drawing Circle"); } } Shape s = new Circle(); // Runtime polymorphism s.draw(); // "Drawing Circle" — not "Drawing Shape"
4. Abstraction — Implementation details chhupana, sirf essential features dikhana. abstract class ya interface se achieve hota hai.
abstract class Vehicle { abstract void start(); // "kaise start hoga" chhupa hai void stop() { System.out.println("Stopping"); } } class Car extends Vehicle { void start() { System.out.println("Car starts with key"); } }
| Aspect | Encapsulation | Abstraction |
|---|---|---|
| Definition | Data hiding + bundling | Implementation hiding |
| Focus | How data is stored/protected | What an object does |
| Achieved by | private fields + getters/setters | abstract class / interface |
| Level | Class level | Design level |
Runtime polymorphism Dynamic Method Dispatch ke through kaam karta hai. JVM compile time pe reference type dekhta hai, but runtime pe actual object type ke basis pe method call karta hai.
Internally, JVM vtable (virtual table) use karta hai — ek table of method pointers. Jab koi method call hoti hai, JVM vtable se actual implementation ka pointer uthata hai.
Animal a = new Dog(); // compile time: type = Animal a.sound(); // runtime: JVM checks Dog's vtable → Dog.sound()
IS-A = Inheritance. Dog IS-A Animal.
HAS-A = Composition. Car HAS-A Engine.
// IS-A class Dog extends Animal { } // Dog IS-A Animal // HAS-A class Car { private Engine engine; // Car HAS-A Engine (Composition) }
Java mein class ke through multiple inheritance allowed nahi hai kyunki Diamond Problem aata hai:
// Assume ye allowed hota — INVALID Java code class A { void greet() { print("A"); } } class B extends A { void greet() { print("B"); } } class C extends A { void greet() { print("C"); } } class D extends B, C { } // D.greet() → B ka lega ya C ka?
default methods aaye, par conflict hone pe class override karna mandatory hai.interface B { default void greet() { print("B"); } } interface C { default void greet() { print("C"); } } class D implements B, C { public void greet() { B.super.greet(); } // must override }
Java 5 ke baad, overriding method parent ke return type ka subtype return kar sakti hai. This is called covariant return type.
class Animal { Animal create() { return new Animal(); } } class Dog extends Animal { @Override Dog create() { return new Dog(); } // Dog is subtype of Animal ✓ }
Java mein every class implicitly java.lang.Object extend karti hai. Key methods:
| Method | Purpose | Override? |
|---|---|---|
equals(Object o) | Logical equality check | YES (always with hashCode) |
hashCode() | Hash value for collections | YES (with equals) |
toString() | String representation | YES |
clone() | Object copy | YES (implement Cloneable) |
finalize() | GC se pehle call | Deprecated Java 9+ |
getClass() | Runtime class info | NO (final) |
wait()/notify() | Thread synchronization | NO |
Shallow Copy — Reference copy. New object banta hai lekin andar ke references same object point karte hain.
Deep Copy — Complete copy. Andar ke objects bhi naye bante hain.
class Address { String city; Address(String c) { city = c; } } class Person implements Cloneable { String name; Address address; // SHALLOW COPY (default clone) protected Object shallowClone() throws CloneNotSupportedException { return super.clone(); // address reference copied, not new object } // DEEP COPY protected Object deepClone() throws CloneNotSupportedException { Person cloned = (Person) super.clone(); cloned.address = new Address(this.address.city); // new Address object return cloned; } }
Marker Interface = Koi method nahi, sirf "tagging" ke liye use hota hai. JVM ya framework isko check karke behavior decide karta hai.
interface Serializable { } // No methods — just a tag! interface Cloneable { } interface Remote { } // java.rmi
@Override, @Deprecated). But Serializable abhi bhi widely used hai.| Letter | Principle | Simple Meaning |
|---|---|---|
| S | Single Responsibility | Class ka sirf ek kaam — Invoice class sirf invoice handle kare, print mat kare |
| O | Open/Closed | Extension ke liye open, modification ke liye closed — new shape add karo, existing code mat badlo |
| L | Liskov Substitution | Subclass parent ki jagah safely use ho sake — Rectangle ko Square se extend mat karo |
| I | Interface Segregation | Ek bada interface mat banao — Robot ko eat() implement karne pe majboor mat karo |
| D | Dependency Inversion | High-level module low-level pe depend na kare — abstractions pe depend karo |
| Aspect | Aggregation | Composition |
|---|---|---|
| Relationship | HAS-A (weak) | HAS-A (strong) |
| Lifecycle | Child can exist without parent | Child dies with parent |
| Example | Department HAS-A Professor (professor exists after dept closed) | House HAS-A Room (room destroyed if house demolished) |
| In code | Reference passed from outside | Object created inside class |
Dog d = new Dog();Class.forName("Dog").newInstance()Dog d2 = (Dog) d.clone();Integer.valueOf(5) (reuses cached objects)The perennial favourite. We settle, once and for all, which to use when, and what the JVM permits in each case.
| Feature | Abstract Class | Interface |
|---|---|---|
| Instantiation | No | No |
| Methods | Abstract + Concrete both | Abstract + default + static (Java 8+) |
| Variables | Any type | public static final only |
| Constructor | Yes | No |
| Inheritance | Single (extends) | Multiple (implements) |
| Access modifiers | Any | public only (by default) |
| When to use | Shared code + template pattern | Contract / capability definition |
Java 8 mein Interface ko sirf abstract methods ki jagah default aur static methods bhi allow kiye gaye.
interface Greeter { void greet(String name); // abstract — implement karna mandatory default void greetAll(String... names) { // default — override optional for (String n : names) greet(n); } static Greeter formal() { // static — interface se call karo return name -> System.out.println("Good day, " + name); } }
Java 9 mein private methods bhi interfaces mein aaye — default methods ke liye helper code likhne ke liye.
Exactly one abstract method wala interface Functional Interface hota hai. @FunctionalInterface annotation optional lekin recommended hai.
@FunctionalInterface interface Calculator { int calculate(int a, int b); // only ONE abstract method } // Lambda = anonymous implementation of Functional Interface Calculator add = (a, b) -> a + b; Calculator multiply = (a, b) -> a * b; System.out.println(add.calculate(3, 4)); // 7
Runnable, Callable, Comparator, Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>No constructor in interface — interfaces instantiate nahi hote, isliye constructor meaningless hai.
No mutable state — Interface ke fields automatically public static final hote hain (constants). Class-level state maintain nahi ho sakti.
interface Config { int MAX_SIZE = 100; // automatically: public static final int MAX_SIZE = 100; // int count = 0; ← This is ALSO final, can't be changed! }
Template Method (Abstract Class) — Algorithm ka skeleton define karo, steps subclass mein customize karo:
abstract class DataMiner { final void mine() { // template method — final so can't be overridden String data = extractData(); String parsed = parseData(data); analyzeData(parsed); } abstract String extractData(); // subclass implements abstract String parseData(String d); void analyzeData(String d) { System.out.println("Analyzing: " + d); } }
Strategy (Interface) — Algorithm को runtime pe swap karo:
interface SortStrategy { void sort(int[] arr); } class Sorter { private SortStrategy strategy; void setStrategy(SortStrategy s) { strategy = s; } void sort(int[] arr) { strategy.sort(arr); } } // Runtime mein strategy change kar sakte hain!
Abstract class ka constructor subclass object creation ke time call hota hai via super(). Yeh common initialization code share karne ke liye use hota hai.
abstract class Animal { protected String name; protected int age; Animal(String name, int age) { // constructor in abstract class this.name = name; this.age = age; System.out.println("Animal created: " + name); } } class Dog extends Animal { Dog(String name, int age) { super(name, age); // Abstract class constructor called here } } // new Dog("Rex", 3) → prints "Animal created: Rex"
Memory, class-loading, the static block, and the precise order in which a Java object is born. The hidden machinery.
| Feature | Static | Instance |
|---|---|---|
| Memory | Method Area (Class Area) | Heap (per object) |
| When loaded | Class loading time | Object creation time |
| Per object? | No — shared by all | Yes — each object has own copy |
| Access | ClassName.member | objectRef.member |
| Can access instance? | No — no 'this' | Yes |
class Counter { static int totalCount = 0; // shared — Method Area int id; // per object — Heap Counter() { totalCount++; id = totalCount; } } Counter c1 = new Counter(); // totalCount=1, c1.id=1 Counter c2 = new Counter(); // totalCount=2, c2.id=2
Static block class loading ke time execute hota hai — object creation se pehle, top to bottom order mein.
class Demo { static int x; static { x = 10; System.out.println("Static Block 1: x = " + x); } static { x = 20; System.out.println("Static Block 2: x = " + x); } Demo() { System.out.println("Constructor"); } } /* Output when: new Demo(); new Demo(); Static Block 1: x = 10 ← only once! Static Block 2: x = 20 ← only once! Constructor Constructor ← twice */
Ek constructor se doosra constructor call karna = Constructor Chaining.
this() — Same class ke doosre constructor ko call karnasuper() — Parent class ke constructor ko call karnaclass Employee { String name; String dept; double salary; Employee(String name) { this(name, "General"); // calls 2-arg constructor } Employee(String name, String dept) { this(name, dept, 30000.0); // calls 3-arg constructor } Employee(String name, String dept, double salary) { this.name = name; // actual initialization this.dept = dept; this.salary = salary; System.out.println("Employee created: " + name); } } class Manager extends Employee { int teamSize; Manager(String name, int teamSize) { super(name, "Management", 60000.0); // parent constructor this.teamSize = teamSize; } }
this() ya super() constructor ka FIRST statement hona chahiye. Dono ek saath nahi ho sakte.
Static methods override nahi hoti — woh method hiding (shadowing) karti hain. Dispatch compile-time pe hota hai, runtime pe nahi.
class Parent { static void greet() { System.out.println("Parent static"); } void hello() { System.out.println("Parent instance"); } } class Child extends Parent { static void greet() { System.out.println("Child static"); } // HIDING @Override void hello() { System.out.println("Child instance"); } // OVERRIDING } Parent p = new Child(); p.greet(); // "Parent static" ← compile-time type = Parent! p.hello(); // "Child instance" ← runtime type = Child!
Singleton = Class ka sirf ek instance ho puri application mein.
// Best approach: Bill Pugh Singleton (Initialization-on-demand holder) public class Singleton { private Singleton() { } // private constructor private static class Holder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; // lazy, thread-safe, no sync overhead } } // Double-Checked Locking (less preferred) public class Singleton2 { private volatile static Singleton2 instance; // volatile important! public static Singleton2 getInstance() { if (instance == null) { synchronized (Singleton2.class) { if (instance == null) instance = new Singleton2(); } } return instance; } }
enum EnumSingleton { INSTANCE; } // Thread-safe, Serialization-safe
Compile-time versus runtime polymorphism — the rules, the edge cases, and the traps that catch the unwary.
| Feature | Overloading | Overriding |
|---|---|---|
| Type | Compile-time polymorphism | Runtime polymorphism |
| Class | Same class (or inheritance) | Parent-Child only |
| Method signature | Must differ (params) | Must be SAME |
| Return type | Can be different | Same or covariant |
| Access modifier | No restriction | Can't reduce visibility |
| Exception | Any exception | Only same/narrower checked exceptions |
| static/final? | Can overload | Can't override |
No! Sirf return type change karne se overloading nahi hoti — compile error aata hai. Compiler method signature (name + parameter types) se methods differentiate karta hai, return type se nahi.
class Test { int getValue() { return 1; } // double getValue() { return 1.0; } ← COMPILE ERROR — ambiguous // Ye overloading hai (parameters different): int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } // ✓ valid }
@Override compiler ko instruction deta hai ki "yeh method parent mein exist karna chahiye". Benefits:
class Dog extends Animal { @Override void soound() { ... } // COMPILE ERROR — typo! Parent has sound() not soound() // Without @Override: silently becomes a NEW method, not override! }
| Exception Type | Override Rule |
|---|---|
| Checked Exception | Override method same ya narrower checked exception throw kar sakta hai. Broader ya new checked exception nahi. |
| Unchecked Exception | Koi restriction nahi — koi bhi RuntimeException throw kar sakte hain |
| No exception in parent | Child checked exception throw nahi kar sakta; unchecked kar sakta hai |
class Parent { void read() throws IOException { } } class Child extends Parent { void read() throws FileNotFoundException { } // ✓ narrower // void read() throws Exception { } ← ✗ broader — COMPILE ERROR // void read() throws RuntimeException { } ← ✓ unchecked OK }
Java, as it turns out, is always pass-by-value. But with object references, the truth is more subtle than the platitude.
Java is always Call by Value — always! Lekin object ke case mein, reference की value copy hoti hai.
// Primitives — pure call by value void change(int x) { x = 100; } int a = 5; change(a); System.out.println(a); // 5 — unchanged! New copy of 5 passed // Objects — reference value copied (NOT the object) void modify(StringBuilder sb) { sb.append(" World"); // ✓ modifies original — same object sb = new StringBuilder("Hello"); // ✗ local reassignment only } StringBuilder s = new StringBuilder("Hello"); modify(s); System.out.println(s); // "Hello World" — append worked, reassign didn't
String immutable hai kyunki:
String s1 = "Hello"; // String Pool mein jaata hai String s2 = "Hello"; // Same pool reference String s3 = new String("Hello"); // Heap mein new object System.out.println(s1 == s2); // true (same pool reference) System.out.println(s1 == s3); // false (different object) System.out.println(s1.equals(s3)); // true (same content) s1 = s1 + " World"; // s1 doesn't change! New String object created
| Feature | String | StringBuilder | StringBuffer |
|---|---|---|---|
| Mutability | Immutable | Mutable | Mutable |
| Thread Safe | Yes | No | Yes (synchronized) |
| Performance | Slow (new objects) | Fast | Moderate |
| Use when | Few/no modifications | Single thread, many changes | Multi-thread, many changes |
Three words that share letters and little else. Frequently confused; rarely forgiven.
| Keyword | Used for | Purpose |
|---|---|---|
final | Variable, Method, Class | Prevent modification/override/inheritance |
finally | try-catch block | Always executes — cleanup code |
finalize() | Method (deprecated) | GC se pehle cleanup — avoid karein |
// final final int MAX = 100; // constant — can't reassign final class String { } // can't extend final void doSomething() {} // can't override // finally try { // risky code } catch (Exception e) { // handle } finally { // ALWAYS runs — even if return/exception in try or catch connection.close(); // cleanup here } // finalize (DEPRECATED — don't use!) protected void finalize() throws Throwable { // called by GC before collecting — unpredictable, slow }
Finally almost always executes, lekin in cases mein nahi:
System.exit() call — JVM terminate ho jaata haiRuntime.halt() — abrupt JVM shutdowntry { System.exit(0); // JVM terminates here } finally { System.out.println("This NEVER prints"); // skipped! }
int test() { try { return 1; } finally { return 2; } // returns 2! finally overrides try's return }
Java 7+ mein try-with-resources AutoCloseable implement karne wali resources ko automatically close karta hai.
// Old way — verbose and error-prone BufferedReader br = null; try { br = new BufferedReader(new FileReader("file.txt")); } finally { if (br != null) br.close(); // Can itself throw exception! } // New way — clean, automatic try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { String line = br.readLine(); } // br.close() called automatically — even if exception thrown
Blank final variable = Declare karo lekin turant initialize mat karo. Constructor mein initialize karna mandatory hai.
class Circle { final double radius; // blank final — no value yet Circle(double r) { radius = r; // must initialize in constructor } // Cannot have: Circle() { } ← compiler error — radius not initialized }
When to choose which, and why. With the Big-O comparison that should be tattooed upon the mind of every Java engineer.
ArrayList internally ek Object array maintain karta hai.
Arrays.copyOf() internally called// ArrayList internal structure (simplified) class ArrayList<E> { private Object[] elementData; // internal array private int size; void add(E e) { if (size == elementData.length) { // grow by 1.5x: newCapacity = oldCapacity + (oldCapacity >> 1) elementData = Arrays.copyOf(elementData, (size * 3) / 2 + 1); } elementData[size++] = e; } E get(int index) { return (E) elementData[index]; // O(1) — direct array access } }
new ArrayList<>(1000) — resizing avoid hogi
Java's LinkedList is a Doubly Linked List. Har node mein:
item — actual dataprev — previous node referencenext — next node reference// LinkedList Node (internal) private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } // LinkedList maintains head and tail pointers Node<E> first; // head Node<E> last; // tail
| Operation | ArrayList | LinkedList | Why? |
|---|---|---|---|
| get(index) | O(1) | O(n) | Array direct access vs traverse |
| add(end) | O(1) amortized | O(1) | Both fast at end |
| add(beginning) | O(n) | O(1) | ArrayList shifts all elements |
| add(middle) | O(n) | O(n) | ArrayList shifts; LL traverses |
| remove(end) | O(1) | O(1) | Both fast |
| remove(middle) | O(n) | O(n) | Both need traversal |
| search (contains) | O(n) | O(n) | Both linear scan |
| Memory | Less (no pointers) | More (prev+next pointers) | Each LL node = 3 objects |
| Cache performance | Better | Worse | Array is contiguous in memory |
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); // 1. Index-based — OK for ArrayList, BAD for LinkedList for (int i = 0; i < list.size(); i++) { ... } // 2. for-each — uses Iterator internally, safe for reading for (String s : list) { ... } // 3. Iterator — only safe way to remove during iteration Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("b")) it.remove(); // ✓ SAFE } // ConcurrentModificationException — structural mod during for-each for (String s : list) { if (s.equals("b")) list.remove(s); // ✗ THROWS CME! } // Java 8 solution list.removeIf(s -> s.equals("b")); // ✓ Safe and clean
Buckets, hashing, collisions, treeification, the sacred contract between equals and hashCode. The chapter that separates senior engineers from the rest.
HashMap internally Array of LinkedList (Buckets) use karta hai. Java 8+ mein jab collision zyada hoti hai toh LinkedList Red-Black Tree mein convert ho jaati hai.
put(key, value) steps:
hashCode() compute karohash = hash ^ (hash >>> 16)index = hash & (capacity - 1)// Simplified HashMap internals class HashMap<K, V> { Node<K, V>[] table; // Array of buckets int size; static final float DEFAULT_LOAD_FACTOR = 0.75f; static final int DEFAULT_INITIAL_CAPACITY = 16; static class Node<K, V> { final int hash; final K key; V value; Node<K, V> next; // linked list for collision } void put(K key, V value) { int hash = key.hashCode() ^ (key.hashCode() >>> 16); int index = hash & (table.length - 1); // bucket index // Check for existing key for (Node<K,V> n = table[index]; n != null; n = n.next) { if (n.hash == hash && n.key.equals(key)) { n.value = value; // update existing return; } } // Add new node to bucket table[index] = new Node(hash, key, value, table[index]); size++; // Check if resize needed if (size > table.length * DEFAULT_LOAD_FACTOR) resize(); } }
Load Factor = threshold ratio. Default: 0.75.
Formula: resize when: size > capacity × loadFactor
Default HashMap: 16 capacity × 0.75 = 12 entries ke baad resize
Resize kaise hota hai:
| Load Factor | Effect | Tradeoff |
|---|---|---|
| Low (e.g., 0.5) | Fewer collisions, more memory | Speed ↑, Memory ↑ |
| High (e.g., 0.9) | More collisions, less memory | Speed ↓, Memory ↓ |
| 0.75 (default) | Good balance | Recommended |
Java 8 se pehle: Collision = LinkedList (worst case O(n))
Java 8 ke baad: Jab ek bucket mein 8 se zyada entries aati hain, LinkedList Red-Black Tree mein convert ho jaati hai → O(log n)
static final int TREEIFY_THRESHOLD = 8; // 8+ entries → convert to tree static final int UNTREEIFY_THRESHOLD = 6; // resize ho toh tree → list back static final int MIN_TREEIFY_CAPACITY = 64; // total capacity ≥ 64 needed
Mutable key ka hashCode change ho sakta hai modification ke baad — toh HashMap key ko dhundh nahi payega!
class MutableKey { String data; int hashCode() { return data.hashCode(); } } Map<MutableKey, String> map = new HashMap<>(); MutableKey key = new MutableKey(); key.data = "hello"; map.put(key, "World"); // stored at bucket for "hello".hashCode() key.data = "bye"; // key MUTATED! hashCode now different! map.get(key); // looks in WRONG bucket → null!
HashSet internally HashMap ka wrapper hai! Har element HashMap key ki tarah store hota hai, value ek dummy constant object hota hai.
class HashSet<E> { private final HashMap<E, Object> map = new HashMap<>(); private static final Object PRESENT = new Object(); // dummy value boolean add(E e) { return map.put(e, PRESENT) == null; // key = element, value = dummy } boolean contains(Object o) { return map.containsKey(o); } }
| Feature | HashMap | ConcurrentHashMap | Hashtable |
|---|---|---|---|
| Null keys | 1 allowed | NOT allowed | NOT allowed |
| Null values | Multiple allowed | NOT allowed | NOT allowed |
| Thread safe | No | Yes (segment locks) | Yes (full lock) |
| Performance | Fast (single thread) | Good (concurrent) | Slow (full sync) |
// HashMap null handling Map<String, Integer> map = new HashMap<>(); map.put(null, 1); // ✓ null key goes to bucket 0 map.put("a", null); // ✓ null value allowed
Contract:
a.equals(b) → a.hashCode() == b.hashCode() MUST be truea.hashCode() == b.hashCode() → a.equals(b) may or may not be true (collision OK)// VIOLATION: equals without hashCode override class BadKey { int id; public boolean equals(Object o) { return ((BadKey) o).id == this.id; } // hashCode NOT overridden → inherits Object.hashCode (memory address) } BadKey k1 = new BadKey(); k1.id = 1; BadKey k2 = new BadKey(); k2.id = 1; k1.equals(k2); // true (same id) Set<BadKey> set = new HashSet<>(); set.add(k1); set.contains(k2); // FALSE!! Different hashCode → different bucket!
Agar sabhi keys ka same hashCode ho (hash collision attack), sab ek hi bucket mein chain banti hain → O(n).
// All go to same bucket — worst case O(n) Map<Integer, String> map = new HashMap<>(); for (int i = 0; i < 1000; i++) { map.put(i * 16, "val"); // all map to bucket 0 with capacity 16! }
Java 8 solution: Treeify at 8 — O(n) → O(log n) per bucket. Full O(n) still possible if all buckets have trees.
ConcurrentHashMap (thread-safe) or ensure good hashCode() distribution. Objects.hash() use karo for multi-field hashCode.Custom object as HashMap key banane ke liye:
equals() override karohashCode() override karo (consistent with equals)class Point { private final int x, y; // final = immutable Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Point)) return false; Point p = (Point) o; return x == p.x && y == p.y; } @Override public int hashCode() { return Objects.hash(x, y); // good hash combining both fields } } Map<Point, String> map = new HashMap<>(); map.put(new Point(1, 2), "origin"); map.get(new Point(1, 2)); // "origin" ✓ — equals+hashCode both correct
Map<String, List<Integer>> map = new HashMap<>(); // get — null return karta hai if key missing map.get("a"); // null // getOrDefault — default value deta hai map.getOrDefault("a", new ArrayList<>()); // empty list (not stored) // computeIfAbsent — stores AND returns default map.computeIfAbsent("a", k -> new ArrayList<>()).add(1); // map now has "a" → [1] // merge — common pattern: word frequency count Map<String, Integer> freq = new HashMap<>(); freq.merge("apple", 1, Integer::sum); // increment by 1, or put 1 if absent
Insertion-order iteration — a doubly-linked list and a HashMap, dancing in perfect harmony.
LinkedHashMap = HashMap + Doubly Linked List to maintain insertion order.
Har Entry mein HashMap ke buckets ke saath, ek before/after pointer bhi hota hai — doubly linked list maintain karta hai insertion order ke liye.
// LinkedHashMap — insertion order preserved Map<String, Integer> linked = new LinkedHashMap<>(); linked.put("banana", 2); linked.put("apple", 1); linked.put("cherry", 3); System.out.println(linked); // {banana=2, apple=1, cherry=3} — insertion order! // LRU Cache — access order mode Map<String, Integer> lru = new LinkedHashMap<>(16, 0.75f, true) { protected boolean removeEldestEntry(Map.Entry<String,Integer> eldest) { return size() > 3; // keep max 3 entries } };
LinkedHashSet = HashSet + insertion order. Internally LinkedHashMap backed hai.
Set<String> set = new LinkedHashSet<>(); set.add("c"); set.add("a"); set.add("b"); System.out.println(set); // [c, a, b] — insertion order maintained // Use case: Deduplicate while preserving order List<String> withDupes = Arrays.asList("x", "y", "x", "z", "y"); Set<String> unique = new LinkedHashSet<>(withDupes); System.out.println(unique); // [x, y, z] — unique, insertion order
Self-balancing binary search trees, O(log n) operations, and the eternal debate: Comparable, or Comparator?
TreeSet internally TreeMap use karta hai jo Red-Black Tree par based hai.
Red-Black Tree properties:
TreeSet<Integer> ts = new TreeSet<>(); ts.add(5); ts.add(3); ts.add(8); ts.add(1); ts.add(7); System.out.println(ts); // [1, 3, 5, 7, 8] — always sorted! // TreeSet navigation methods ts.first(); // 1 — smallest ts.last(); // 8 — largest ts.floor(6); // 5 — largest ≤ 6 ts.ceiling(6); // 7 — smallest ≥ 6 ts.headSet(5); // [1, 3] — elements < 5 ts.tailSet(5); // [5, 7, 8] — elements ≥ 5 ts.subSet(3, 8); // [3, 5, 7] — range [3, 8)
| Feature | Comparable | Comparator |
|---|---|---|
| Package | java.lang | java.util |
| Method | compareTo(T o) | compare(T o1, T o2) |
| Class modified? | Yes (intrusive) | No (external) |
| Sorting type | Natural ordering (1 only) | Multiple custom orderings |
| Use when | You own the class | 3rd party class or multiple sorts |
// Comparable — natural order class Student implements Comparable<Student> { String name; int age; public int compareTo(Student other) { return Integer.compare(this.age, other.age); // sort by age } } // Comparator — custom, multiple orderings Comparator<Student> byName = Comparator.comparing(s -> s.name); Comparator<Student> byAgeDesc = Comparator.comparingInt(Student::getAge).reversed(); Comparator<Student> byNameThenAge = Comparator.comparing(Student::getName) .thenComparingInt(Student::getAge); list.sort(byName); TreeSet<Student> ts = new TreeSet<>(byName); // custom order
TreeSet null insert karne pe NullPointerException throw karta hai (Java 7+). Kyunki compareTo() null pe call hota hai jo NPE deta hai.
TreeSet<String> ts = new TreeSet<>(); ts.add(null); // NullPointerException! // Workaround: Custom Comparator that handles null TreeSet<String> ts2 = new TreeSet<>( Comparator.nullsFirst(Comparator.naturalOrder()) ); ts2.add(null); ts2.add("b"); ts2.add("a"); System.out.println(ts2); // [null, a, b]
Java's PriorityQueue by default Min-Heap hai (smallest element at top). Internally array-based binary heap hai.
// Min-Heap (default) PriorityQueue<Integer> minHeap = new PriorityQueue<>(); minHeap.offer(5); minHeap.offer(1); minHeap.offer(3); minHeap.poll(); // 1 (minimum) // Max-Heap PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); maxHeap.offer(5); maxHeap.offer(1); maxHeap.offer(3); maxHeap.poll(); // 5 (maximum) // Custom object priority PriorityQueue<Task> pq = new PriorityQueue<>( Comparator.comparingInt(Task::getPriority) );
A reference for the hurried — and for those who, having memorised, wish to confirm what they already know.
| get(i) | O(1) |
| add(e) | O(1) amortized |
| add(i, e) | O(n) |
| remove(i) | O(n) |
| contains() | O(n) |
| get(i) | O(n) |
| addFirst / addLast | O(1) |
| removeFirst / removeLast | O(1) |
| remove(i) | O(n) |
| contains() | O(n) |
| get / put / remove | O(1) avg |
| Worst case | O(n) |
| Iteration | O(n) |
| Resize | O(n) |
| get / put / remove | O(1) |
| Insertion-order iter. | O(n) |
| Access-order (LRU) | O(1) |
| get / put / remove | O(log n) |
| Iteration | O(n) |
| firstKey / lastKey | O(log n) |
| offer / add | O(log n) |
| poll / remove | O(log n) |
| peek | O(1) |
| remove(i) | O(n) |
The patterns that recur in Xperi interviews, distilled from Glassdoor, from quora, from the whisperings of those who have faced the panel.
class LRUCache { private final int capacity; private final LinkedHashMap<Integer, Integer> cache; LRUCache(int capacity) { this.capacity = capacity; // accessOrder=true: moves accessed entry to end this.cache = new LinkedHashMap<>(capacity, 0.75f, true) { protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest) { return size() > capacity; // remove LRU when full } }; } public int get(int key) { return cache.getOrDefault(key, -1); // moves to end (most recent) } public void put(int key, int value) { cache.put(key, value); // auto removes eldest if over capacity } } // Test LRUCache lru = new LRUCache(2); lru.put(1, 10); lru.put(2, 20); lru.get(1); // 10 — key 1 is now most recent lru.put(3, 30); // evicts key 2 (least recently used) lru.get(2); // -1 — evicted!
public char firstNonRepeating(String s) { // LinkedHashMap preserves insertion order Map<Character, Integer> freq = new LinkedHashMap<>(); for (char c : s.toCharArray()) { freq.merge(c, 1, Integer::sum); } for (Map.Entry<Character, Integer> e : freq.entrySet()) { if (e.getValue() == 1) return e.getKey(); } return '\0'; } // "aabbcde" → 'c'; Time: O(n), Space: O(1) — max 26 chars
public List<List<String>> groupAnagrams(String[] words) { Map<String, List<String>> map = new HashMap<>(); for (String word : words) { char[] chars = word.toCharArray(); Arrays.sort(chars); String key = new String(chars); // "eat","tea","ate" → "aet" map.computeIfAbsent(key, k -> new ArrayList<>()).add(word); } return new ArrayList<>(map.values()); } // Input: ["eat","tea","tan","ate","nat","bat"] // Output: [["eat","tea","ate"],["tan","nat"],["bat"]] // Time: O(n·k·log k) where k = max word length
public int[] topKFrequent(int[] nums, int k) { // Step 1: Count frequencies Map<Integer, Integer> freq = new HashMap<>(); for (int n : nums) freq.merge(n, 1, Integer::sum); // Step 2: Min-heap of size k PriorityQueue<Integer> pq = new PriorityQueue<>( Comparator.comparingInt(freq::get) ); for (int key : freq.keySet()) { pq.offer(key); if (pq.size() > k) pq.poll(); // remove least frequent } int[] result = new int[k]; for (int i = k - 1; i >= 0; i--) result[i] = pq.poll(); return result; } // Time: O(n log k), Space: O(n+k)
public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> seen = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (seen.containsKey(complement)) { return new int[] { seen.get(complement), i }; } seen.put(nums[i], i); // store: value → index } throw new IllegalArgumentException("No solution"); } // Time: O(n), Space: O(n) — one pass HashMap
| Question | Answer |
|---|---|
| Stack implementation prefer? | ArrayDeque (not Stack class — synchronized) |
| Queue implementation prefer? | ArrayDeque or LinkedList |
| Collections.sort() algorithm? | TimSort — O(n log n) — stable sort |
| Arrays.sort() for primitives? | Dual-Pivot QuickSort — O(n log n) avg |
| fail-fast vs fail-safe iterator? | ArrayList = fail-fast (throws CME); CopyOnWriteArrayList = fail-safe |
| Unmodifiable collection? | Collections.unmodifiableList() or List.of() (Java 9+) |
| Synchronized list? | Collections.synchronizedList(new ArrayList<>()) |
| HashMap initial capacity formula? | (expectedSize / loadFactor) + 1 |
| TreeMap vs HashMap? | TreeMap: sorted O(log n). HashMap: unsorted O(1) |
| EnumSet internal? | BitVector — fastest Set for enums |