OOP for DSA & Design Rounds
Nested Node classes, constructors, equals/hashCode, records, generics and enums: just enough OOP to write clean DSA code and pass design rounds.
▶ guided lesson⚡ 1-minute summary↻ 14 recall cards
before this: Java Memory
01 · why it exists
Why this exists, and the weight it carries
since1962–1967
Classes, objects & encapsulation
Ole-Johan Dahl and Kristen Nygaard
Dahl and Nygaard, at the Norwegian Computing Center, wanted to describe and simulate real systems. Simula I, a simulation language, and Simula 67, a general-purpose language built on ALGOL 60, introduced classes, objects and inheritance. In 2001 they received the ACM Turing Award "for ideas fundamental to the emergence of object oriented programming". Java's class, new and extends are those same three ideas.
the problem it solvedKeep each piece of state together with the only code allowed to change it, and create as many independent copies (objects) as the program needs.
its jobs today
- Every design-round answer: a class whose fields hold the state and whose methods are the allowed operations.
- Every node-based structure:
ListNode,TreeNode,TrieNodeand graph nodes are objects linked by references. - Encapsulation protects invariants: a
ParkingSystemcan never drop below 0 free slots, because onlyaddCarchanges the count.
the weight it carries
Illustrative: Design Parking System allows up to 1,000 slots per size and 1,000 calls. Modelling every slot as a flag stores 3,000 booleans and can scan 1,000 of them per call: up to 1,000 × 1,000 = 10⁶ checks. Storing only what the operations need is 3 ints and 1 check per call: 1,000 checks in total.
without it
State and the code that guards it drift apart: any line could set a count to −1, and design answers become bags of global variables.
when · why · how
- when
- Any "Design / Implement the X class" problem, and every node-based structure.
- why
- Grouping state with the methods that change it keeps every operation small, O(1) to reason about, and impossible to bypass.
- how
privatefields for the state, a constructor that builds a valid object, one public method per operation, and instance fields (notstatic) for anything per object.
since1997
Static nested vs inner classes
Sun Microsystems (the JDK 1.1 Inner Classes Specification)
Java 1.0 had only top-level classes. JDK 1.1 added classes declared inside other classes: "nested top-level" classes (today's static nested classes) and inner classes, whose objects belong to an enclosing object. The specification explains that the compiler links each inner object to its outer object through an extra hidden field. That hidden link is exactly why DSA code writes static class Node.
the problem it solvedKeep helper classes (nodes, entries, iterators) next to the one class that uses them, without cluttering the top level.
its jobs today
- Node classes inside data structures: the JDK's own
LinkedListandHashMapkeep their nodes in static nested classes. - Iterators:
ArrayList's iterator is an inner class, because it must read the list's fields directly.
the weight it carries
Illustrative: compiled for Java 17, 10⁶ nodes of an inner (non-static) Node class carry 10⁶ hidden references to the outer object, one per node, that the algorithm never reads (javac drops an unused one only when targeting Java 18+). A static nested Node carries none, and it can be created from static methods.
without it
Every helper would be a separate top-level class (clutter, and clashes like two different Nodes), or every node would drag a hidden outer reference around.
when · why · how
- when
- You need a Node, Entry or TrieNode class inside your Solution or design class.
- why
staticnested: no hidden field and no outer object needed. Inner: one hidden reference per object, worth it only when the object reads the outer object's fields.- how
private static class Node { int val; Node next; Node(int v) { val = v; } }inside your class. Dropstaticonly for iterator-style helpers.
since1953 (hashing) · 1996 (Java's contract)
The equals/hashCode contract
Hans Peter Luhn (IBM); the designers of java.lang.Object
As Knuth records, hashing goes back to an internal IBM memo by H. P. Luhn in January 1953, which also suggested chaining colliding keys together. Java built hashing into every object: Object has both equals and hashCode, and its specification, from the first Java Language Specification (1996) to today's documentation, states the contract that equal objects must have equal hash codes. HashMap and HashSet rely on it: the hash code picks a bucket, equals confirms inside it.
the problem it solvedLet any class be a hash-table key, as long as its two methods agree on what "equal" means.
its jobs today
- Every HashMap and HashSet lookup: hashCode chooses the bucket, equals checks the few keys inside it.
- De-duplicating states in BFS or DP (a
Set<State>of visited states) works only if equal states hash equally. - Strings and records already keep the contract, which is why they make good keys.
the weight it carries
Illustrative: 10⁵ distinct keys and 10⁵ lookups. With a good hashCode each lookup checks about 1 key: about 10⁵ checks in total. If hashCode returned a constant, every key would share one bucket and each lookup could check up to 10⁵ keys: up to 10¹⁰ checks.
without it
Hash tables would need a separate hash function for every key type, or would fall back to identity: two equal grid cells would count as two different visited states.
when · why · how
- when
- Your own class goes into a HashMap key, a HashSet, or
contains/remove(Object)on a collection. - why
- A consistent pair gives O(1) average lookups. equals without hashCode makes equal keys miss each other; a constant hashCode makes each lookup up to O(n).
- how
- Override both from the same fields (
31 * r + corObjects.hash), add@Override, keep key fieldsfinal, or simply use a record.
since2020–2021
Records
Gavin Bierman (owner of JEP 395), reviewed and endorsed by Brian Goetz
A simple data class in Java used to need a constructor, getters, equals, hashCode and toString: many lines that hide the one line of intent and are easy to get wrong. JEP 359 previewed records in JDK 14, JEP 384 refined them in JDK 15, and JEP 395 made them final in JDK 16. Java 17, the version this course uses, has them as a standard feature.
the problem it solvedModel plain, immutable data in one line, with equals, hashCode and toString generated correctly.
its jobs today
- Pairs and tuples in DSA: (node, distance) in Dijkstra, (row, col) grid cells, (value, index) while sorting.
- HashMap keys built from several fields, with no hand-written equals or hashCode to get wrong.
- Small immutable data objects in backend code, such as request and response bodies.
the weight it carries
Illustrative: a two-field key class written by hand (fields, constructor, two getters, equals, hashCode, toString) runs to about 30 lines. record Cell(int r, int c) {} is 1 line, with nothing to forget.
without it
People reach for int[] pairs, which break as HashMap keys (identity equality), or hand-write key classes and forget hashCode.
when · why · how
- when
- You need a pair, tuple, grid cell or composite key whose fields never change.
- why
- Equality and hashing are generated from every component, so HashMap and HashSet work at once; the object costs the same as a small class.
- how
record Cell(int r, int c) {}; read withcell.r(); validate in a compact constructor; build a new record to "change" one.
since1998 (GJ) · 2004 (Java 5)
Generics
Gilad Bracha, Martin Odersky, David Stoutamire and Philip Wadler (GJ); JSR 14
Before generics, a Java List held plain Objects: every get needed a cast, and a wrong cast failed only at run time. GJ ("Generic Java", OOPSLA 1998) added type parameters that the compiler checks and then erases, so existing class files kept working. JSR 14 took GJ as its basis for adding generics to Java, delivered with J2SE 5.0 in 2004.
the problem it solvedType-safe collections and algorithms, written once for every object type, without breaking existing code.
its jobs today
- Every collection you use:
List<Integer>,Map<String, List<Integer>>,PriorityQueue<int[]>. - Reusable algorithms:
Collections.max,Collections.sort, and your own<T extends Comparable<T>> T max(List<T>).
the weight it carries
Illustrative: one generic max replaces separate versions for Integer, Long, String and every record you compare: 1 implementation instead of 4 or more, and passing the wrong type is a compile error instead of a ClassCastException in a hidden test.
without it
Every read from a collection needs a cast, and mixed-up types slip through until they crash at run time.
when · why · how
- when
- A class or method works the same way for many element types: a container, a max, a pair.
- why
- The compiler checks types once; erasure leaves plain references at run time, so there is no speed cost, but primitives must be boxed.
- how
class Box<T>,static <T extends Comparable<T>> T max(List<T> xs); writeList<Integer>, neverList<int>; nonew T[n].
since2004
Enums
JSR 201 (submitted by Sun Microsystems; contacts Gilad Bracha and Joshua Bloch)
Before Java 5, constants were plain ints (static final int UP = 0): nothing stopped you passing 7 as a direction, and printing one showed a meaningless number. JSR 201 added enumerations to the language, together with autoboxing, the for-each loop and static import, in J2SE 5.0 (2004). A Java enum is a class with a fixed set of instances that can carry fields and methods.
the problem it solvedType-safe named constants: the compiler rejects anything that is not one of the listed values.
its jobs today
- Directions in grid problems (
UP(-1, 0)), states in DFS (NEW,VISITING,DONE), car or order types in design rounds. switchover states in state machines and parsers;EnumMap(backed by an array) andEnumSet(backed by a bit vector) as compact collections.
the weight it carries
Illustrative: four directions as an enum are 4 objects for the whole program, and an invalid direction does not compile. As raw ints, any of the 2³² int values compiles as a "direction".
without it
Magic numbers: dir = 5 compiles, state == 2 means nothing to a reader, and the bug appears only at run time.
when · why · how
- when
- A value must be one of a small fixed set: directions, states, types, commands.
- why
- Readability and safety at no cost: constants are single objects,
==works, andordinal()orEnumMapindex arrays in O(1). - how
enum Dir { UP(-1, 0), …; final int dr, dc; Dir(int dr, int dc) {…} }; switch on it; cachevalues()when you need it in a loop.
02 · plain english
The idea in 30 seconds
A class is a blueprint; new builds an object from it on the heap. Every linked list and tree you write is objects pointing at objects.
Design questions ("Design a HashSet", "Design a Parking System") test one habit: keep the state in private fields, and make each operation a small method that checks, updates and returns.
HashMap, HashSet and sorting trust your class's equals, hashCode and compareTo. Keep those contracts and the library does the heavy lifting.
picture this
A Google Form: the form (class) defines the questions once, and every submission (object) holds its own answers. A setting printed on the form itself, like the closing date, is shared by every submission: that is a static field.
03 · remember
What you must know
- Class vs object: a field without
staticexists once per object; astaticfield exists once per class, shared by every object. - Constructors build a valid object:
this.size = sizecopies the parameter into the field,this(...)reuses another constructor, andfinalfields are set exactly once. - Encapsulation:
privatefields, changed only through methods that check their input. In design rounds, invalid calls returnfalseor-1exactly as the statement says. - `private static class Node` inside your class: no hidden link to an outer object, so nodes stay small and can be created from anywhere. Drop
staticonly when the nested object must read the outer object's fields (an iterator). - LeetCode's `ListNode` / `TreeNode` are plain classes with fields
val,next,left,rightand three constructors. They are already defined for you: never redeclare them. - Interfaces say what (
Deque,Map,Comparable,Iterator); classes say how. Declare by interface:Deque<Integer> st = new ArrayDeque<>();. - equals/hashCode contract: equal objects must have equal hash codes. A HashMap uses
hashCodeto pick the bucket andequalsto confirm. Override both or neither. - Records (Java 16+) give a constructor, accessors,
equals,hashCodeandtoStringin one line: ideal for pairs, grid cells and HashMap keys. - Generics (
List<T>,<T extends Comparable<T>>) write code once for every object type. Enums give named, type-safe constants (directions, states) that can carry data. - Ordering objects with
ComparableandComparatoris taught in the Java Toolkit chapter.
04 · see it
Play with it in 3D
Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.
Design Linked List: Node objects and next references
The MyLinkedList object holds size and an arrow to a dummy node; each Node holds a value and a next arrow. An insert is two arrow changes, in the right order.
Design HashSet: hashCode picks the bucket, equality confirms
Each call searches one bucket only. With 5 buckets, keys 1, 6, 11 and 16 all land in bucket 1: a collision chain.
One class, many objects: static vs instance fields
Every ParkingLot object has its own free[] counts. The static opened counter has exactly one copy, stored with the class.
05 · cost
Time & space, explained
| Time per call | Memory | why | |
|---|---|---|---|
| HashSet as an ArrayList of keys | O(n) | O(n) | contains walks every stored key, and add/remove call it. |
| HashSet as boolean[10⁶ + 1] | O(1) | O(U), U = 10⁶ + 1 | One flag per possible key, used or not: the memory follows the key range, not the data. |
| HashSet with buckets + chaining | O(1) average, O(n) worst | O(n + B) | A good hash spreads n keys over B buckets, so chains stay short; if every key collides, one chain holds all n. |
| Linked list: singly + dummy + size | O(index) | O(n) | get, add and delete walk from the dummy to the position. |
| Linked list: doubly + two sentinels | O(min(index, n − index)); ends O(1) | O(n) | Walk from the nearer end; the sentinels make both ends instant. |
| Parking system with counters | O(1) | O(1) | Only how many slots are free matters, not which ones. |
| Time | Notes | why | |
|---|---|---|---|
new Node(v) | O(1) | one heap object; fields start as 0, false or null | Allocation plus the constructor's few assignments. |
| Read a field / call a method | O(1) | follows one reference | node.next.val is two reference hops. |
Record equals / hashCode (k fields) | O(k) | generated from every component | Each component is compared or mixed once. |
HashMap.get with a good hashCode | O(1) average | hash → bucket, then equals on a few entries | Keys are spread over many buckets. |
HashMap.get when hashCode is constant | O(n) | every key in one bucket | equals must run against keys one by one (Java 8+ tree bins help only for Comparable keys). |
EnumType.values() | O(k) | returns a fresh copy of the constants | Cache it in a static field if you call it in a loop. |
06 · java
Raw vs Collections Framework
Constructors and encapsulation
class Wallet { // a UPI-style wallet private final String owner; // final: set once, in the constructor private long balancePaise; // private: only the methods below can change it Wallet(String owner) { this(owner, 0); // reuse the other constructor } Wallet(String owner, long openingPaise) { if (openingPaise < 0) throw new IllegalArgumentException("negative opening balance"); this.owner = owner; // this.owner is the field, owner is the parameter this.balancePaise = openingPaise; } boolean pay(long paise) { // every change goes through one checked door if (paise <= 0 || paise > balancePaise) return false; balancePaise -= paise; return true; } long balance() { return balancePaise; } String owner() { return owner; }}this.owner = owner:this.owneris the field,owneris the parameter. Writingowner = ownerassigns the parameter to itself and leaves the field empty.this(owner, 0)calls another constructor of the same class. It must be the first statement.finalfields must be set exactly once, in the constructor; the compiler checks it.privatefields plus methods that validate = encapsulation. The wallet can never go negative, becausepayis the only door.
Static vs instance fields
class ParkingLot { static int opened = 0; // ONE copy, stored with the class private final int[] free; // one copy PER OBJECT: free[1] big, free[2] medium, free[3] small ParkingLot(int big, int medium, int small) { free = new int[] {0, big, medium, small}; opened++; // bumps the shared count } boolean addCar(int carType) { if (free[carType] == 0) return false; free[carType]--; // changes only THIS lot return true; }}- A field without
staticexists once per object; astaticfield exists once per class. The sim shows each lot's ownfree[]and one sharedopened. - Design problems: never keep per-object state in
staticfields. Judges create several objects, and all of them would share one counter. - A
staticfield also survives between calls in the same run, so values from one test can leak into the next.
Static nested classes: static class Node
class LinkedStack { private static class Node { // static nested: no hidden link to a LinkedStack final int val; final Node next; Node(int val, Node next) { this.val = val; this.next = next; } } private Node top; // null means empty private int size; void push(int x) { top = new Node(x, top); // the new node points at the old top size++; } int pop() { int val = top.val; top = top.next; // the old top becomes garbage size--; return val; } int peek() { return top.val; } boolean isEmpty() { return top == null; } int size() { return size; }}static class Nodeis an ordinary class that simply lives inside another one. No hidden reference, sonew Node(5)works anywhere, even instaticmethods.- Mark it
privatewhen only the outer class uses it, asjava.util.LinkedListdoes with its ownNode. - This is the default for every node, entry or trie node you write inside a Solution or design class.
Inner classes: when the object needs its outer object
class Countdown implements Iterable<Integer> { private final int start; Countdown(int start) { this.start = start; } public Iterator<Integer> iterator() { return new Ticker(); // each Ticker is tied to THIS Countdown } private class Ticker implements Iterator<Integer> { // inner (no static): sees the outer object private int next = start; // reads the outer object's field directly public boolean hasNext() { return next >= 0; } public Integer next() { return next--; } }}- An inner class (no
static) stores a hidden reference to its outer object. That is one extra field per object, and an outer object must exist to create one. - Create an inner-class object from a static method and javac stops with "non-static variable this cannot be referenced from a static context".
- Use an inner class only when the nested object really reads the outer object's fields. Iterators are the classic case:
ArrayList's iterator is an inner class.
LeetCode's ListNode and TreeNode
// LeetCode already defines these above your Solution: use them, don't declare them again.class ListNode { int val; // package-private fields: read them directly ListNode next; ListNode() {} ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; }} class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {} TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; }}- LeetCode shows these definitions in a comment above your
Solutionand compiles your code with the real classes. Use them; don't declare your own. - Fields are read and written directly:
node.val,node.next,root.left. No getters. nullmeans "no node": the empty list and the empty tree are bothnull. Check for it first.- A dummy node (
new ListNode()) in front of the head removes the special case for an empty result.
Interfaces: what vs how
interface IntStack { // WHAT a stack can do: no fields, no code void push(int x); int pop(); boolean isEmpty();} class ArrayIntStack implements IntStack { // HOW, version 1: a growing int[] private int[] items = new int[8]; private int size; public void push(int x) { if (size == items.length) items = Arrays.copyOf(items, size * 2); items[size++] = x; } public int pop() { return items[--size]; } public boolean isEmpty() { return size == 0; }} class DequeIntStack implements IntStack { // HOW, version 2: wrap an ArrayDeque private final Deque<Integer> deque = new ArrayDeque<>(); public void push(int x) { deque.push(x); } public int pop() { return deque.pop(); } public boolean isEmpty() { return deque.isEmpty(); }} static int[] reversed(int[] values, IntStack stack) { // works with ANY IntStack for (int v : values) stack.push(v); int[] out = new int[values.length]; for (int i = 0; i < out.length; i++) out[i] = stack.pop(); return out;}- An interface lists methods without bodies (plus optional
defaultmethods). A class thatimplementsit must provide every one, aspublicmethods. reversedworks with anyIntStack: code that depends on the interface does not change when you swap the implementation.- The JDK is built this way:
Deque→ArrayDequeorLinkedList,Map→HashMaporTreeMap. Declare by interface, construct by class.
equals and hashCode: the HashMap contract
class Cell { // a grid position used as a HashSet key final int r; final int c; Cell(int r, int c) { this.r = r; this.c = c; } @Override public boolean equals(Object o) { if (this == o) return true; // same object if (!(o instanceof Cell other)) return false; // null or a different type return r == other.r && c == other.c; // same fields: equal } @Override public int hashCode() { return 31 * r + c; // equal cells get equal hash codes }} class BrokenCell { // equals WITHOUT hashCode final int r; final int c; BrokenCell(int r, int c) { this.r = r; this.c = c; } @Override public boolean equals(Object o) { return o instanceof BrokenCell other && r == other.r && c == other.c; } // no hashCode(): equal cells usually land in different buckets, so HashSet never compares them}- HashMap and HashSet call
hashCode()to choose a bucket, thenequals()inside it. Equal objects with different hash codes land in different buckets and are never compared. - The contract, from
Object's documentation: equal objects must return equal hash codes. Unequal objects may share one; fewer collisions just means faster tables. 31 * r + c(orObjects.hash(r, c), which boxes its arguments) mixes the fields. Use exactly the fields thatequalsuses.- Never change a key's fields while it sits in a HashSet: it is now in the wrong bucket and
containsmisses it. o instanceof Cell other(Java 16+) checks the type and casts in one step.
Records for pairs, cells and keys
record Point(int r, int c) {} // fields, constructor, r(), c(), equals, hashCode, toString record Edge(int to, int weight) { Edge { // compact constructor: validate, fields are set after it if (weight < 0) throw new IllegalArgumentException("negative weight"); } Edge heavier(int extra) { // records are immutable: "change" means a new record return new Edge(to, weight + extra); }} static int distinctCells(int[][] moves) { // Point as a HashSet key: no equals/hashCode to write Set<Point> seen = new HashSet<>(); int r = 0, c = 0; seen.add(new Point(r, c)); for (int[] m : moves) { r += m[0]; c += m[1]; seen.add(new Point(r, c)); } return seen.size();}record Point(int r, int c) {}writes the private final fields, constructor, accessorsr()andc(),equals,hashCodeandtoStringfor you (JEP 395, Java 16).- Records are immutable: to "change" one, build a new one. That is exactly what a HashMap key should be.
- A compact constructor (
Edge { … }) validates the arguments; the fields are assigned after it runs. - In hot loops over a grid you can skip objects entirely and encode a cell as one int:
r * cols + c.
Generics: one implementation for every type
static <T extends Comparable<T>> T max(List<T> items) { // any T that can compare itself to a T T best = items.get(0); for (T item : items) { if (item.compareTo(best) > 0) best = item; } return best;} class Pair<A, B> { // a tiny generic class (a record works too) final A first; final B second; Pair(A first, B second) { this.first = first; this.second = second; }}<T>is a type parameter. The compiler checks the types, then erases them (type erasure), so your code needs no casts.- Bounded:
<T extends Comparable<T>>means "any T that can compare itself to another T". That is what letsmaxcallcompareTo. - Only object types:
List<Integer>, neverList<int>. Primitives get boxed (see What Happens in Memory). - Erasure means no
new T[n]and noinstanceof List<Integer>. Use anArrayList<T>, or anObject[]with a cast inside the class. - The JDK's
Collections.maxuses the more general bound<T extends Object & Comparable<? super T>>.
Enums for directions and states
enum Direction { UP(-1, 0), RIGHT(0, 1), DOWN(1, 0), LEFT(0, -1); // the only four Direction objects that will ever exist private static final Direction[] ALL = values(); // values() copies the array on every call: cache it final int dr; // each constant carries its own data final int dc; Direction(int dr, int dc) { this.dr = dr; this.dc = dc; } Direction turnRight() { return ALL[(ordinal() + 1) % 4]; // clockwise order: UP, RIGHT, DOWN, LEFT } Direction turnLeft() { return ALL[(ordinal() + 3) % 4]; }} static int[] walk(String commands) { // F = forward, R = turn right, L = turn left Direction d = Direction.UP; int r = 0, c = 0; for (char ch : commands.toCharArray()) { switch (ch) { case 'F' -> { r += d.dr; c += d.dc; } case 'R' -> d = d.turnRight(); case 'L' -> d = d.turnLeft(); default -> throw new IllegalArgumentException("unknown command " + ch); } } return new int[] {r, c};}- An enum is a class with a fixed set of instances.
Direction.UPis one object for the whole program, so==is safe. - Constants can carry data (
dr,dc) and methods (turnRight), replacing parallelint[] dr = {…}arrays and magic numbers. ordinal()is the declaration position.values()returns a new array on every call, so cache it in a static field.- States read better as enums:
enum State { NEW, VISITING, DONE }for cycle detection beats 0/1/2.EnumMapis a map backed by an array.
Ordering objects
Comparable(one natural order,compareTo) andComparator(any order you pass in) are taught in the Java Toolkit chapter.- Records and enums plug straight in:
Comparator.comparingInt(Point::r).thenComparingInt(Point::c). - Keep
compareToconsistent withequals:TreeSetandTreeMaptreatcompareTo(...) == 0as a duplicate.
| call | does | cost |
|---|---|---|
new Node(v) | Allocate one object; unset fields are 0, false or null | O(1) |
this.field = param / this(args) | Store a constructor argument / call another constructor (first line) | O(1) |
o instanceof Cell other | Type check and cast in one step (Java 16+) | O(1) |
Objects.hash(a, b) / Objects.equals(a, b) | Quick hashCode from fields (boxes them) / null-safe equals | O(k) |
record P(int a, int b) {} | Immutable data class with equals, hashCode, toString | O(1) per new |
Integer.hashCode(x) | Hash of an int: the value itself | O(1) |
Math.floorMod(h, n) | Bucket index that is never negative | O(1) |
E.values() / e.ordinal() / E.valueOf(name) | All constants (a new array) / position / constant by name | O(k) / O(1) / O(1) |
new EnumMap<>(E.class) | Array-backed map keyed by enum constants | O(1) per operation |
A node class inside your solution
→ private static class Node
No hidden outer reference; can be created from static code.
A pair, tuple or grid cell as a HashMap key
→ A record: record Cell(int r, int c) {}
equals and hashCode are generated from both fields.
Your own class as a HashMap or HashSet key
→ Override equals AND hashCode from the same fields
The table needs both to find an equal key.
A fixed set of options (directions, states, types)
→ An enum
Type-safe, named, can carry data, and == is safe.
Two implementations behind one API
→ An interface + classes that implement it
Callers depend on what, not how.
Per-object state in a design problem
→ Instance fields (no static)
The judge creates several objects; static fields would be shared by all of them.
One algorithm for any comparable type
→ A generic method <T extends Comparable<T>>
Checked at compile time, no casts.
07 · recognise
Spot the pattern
"Design" or "Implement the X class" with a list of methods
Private fields for state, a constructor, one small checked method per operation
You need a Node, TrieNode or Entry inside your class
private static class Node
A pair or grid cell must be a HashMap key or go in a HashSet
A record, or equals + hashCode written together
Four directions, three states, a fixed set of types
An enum (or one constant int[][] table)
"Without using any built-in hash table / LinkedList library"
Build it from an array plus your own Node class
The method takes ListNode head or TreeNode root
LeetCode's classes: read val, next, left, right directly; null is empty
08 · traps & tricks
Mistakes to avoid, tricks to use
traps
- Declaring
class Node(nostatic) inside Solution and callingnew Node()from a static method: compile error. - Using
staticfields for per-object state in a design problem: two objects share one counter. - Overriding
equalswithouthashCode: HashSet and HashMap stop finding equal keys. - Writing
equals(Cell other)instead ofequals(Object o): that overloads instead of overriding, so collections never call it.@Overridecatches it. - Changing a key's fields after putting it in a HashMap or HashSet.
size = size;in a constructor: the parameter shadows the field. Writethis.size = size;.- Redeclaring
ListNodeorTreeNodein a LeetCode answer. List<int>: generics need object types. UseList<Integer>, or anint[].- Calling
values()on an enum inside a hot loop: it allocates a new array every time.
tricks
- In design rounds, write the fields first and say what each one means. The methods then almost write themselves.
- Return exactly what the statement asks for on bad input (
false,-1, do nothing). Judges test those calls. - Put
@Overrideonequals,hashCode,toStringand interface methods, so the compiler catches wrong signatures. - Reach for a record first for pairs and keys; write a full class only when fields must change.
- Keep helper classes
private staticinside your Solution or design class, so everything pastes as one file. - Say the trade-off out loud: "I'm using a dummy node so the head needs no special case."
- Simple hai: fields are the state, methods are the rules.
09 · why & how
Questions you might be too shy to ask
What exactly is this, and when must I write it?
this is the object the method is running on right now. You must write it when a parameter has the same name as a field: this.size = size means "my field = the parameter". You also use this(...) to call another constructor. Everywhere else, size alone already means the field, so this. is optional.
Static nested or inner class: does it really matter in an interview?
Yes, because of the compile error. Inside Solution, if Node is an inner class and you create one from a static helper, javac refuses with "non-static variable this cannot be referenced from a static context". Making it static fixes that and saves a hidden reference per node. Default to static.
Why does LeetCode's ListNode have no private fields and no getters?
It is a data holder for algorithms, and direct field access keeps solutions short. Encapsulation matters in design problems, where the class you write has rules to protect, like "free slots never go below zero".
Interface or abstract class?
For DSA and most design rounds, an interface. A class can implement many interfaces but extend only one class. Reach for an abstract class only when implementations share real code and fields.
Do I need hashCode if my class only goes into a TreeMap?
No: TreeMap and TreeSet use compareTo or a Comparator, never hashCode. But if the class might ever go into a HashMap or HashSet, override both equals and hashCode, or simply make it a record.
Can I use records in interviews and online assessments?
Records need Java 16 or newer. Check the Java version the platform shows; if it is older, use a small class with equals and hashCode, or encode the pair as one number (r * cols + c).
A design problem has no main. Who calls my methods, and why does the output say null?
The judge does. It runs new MyHashSet(), then calls your methods in the order the input lists them, and records what each one returns. The constructor and void methods return nothing, so their slot in the output shows null. Your only job: the object must give the right answer after any sequence of calls.
In Design HashSet the keys are never negative. Why use Math.floorMod instead of %?
For keys 0..10⁶, key % buckets gives the same answer. The habit matters when the hash can be negative (many String.hashCode() values are, because the int overflows): then % returns a negative index and the array access throws. Math.floorMod(h, n) always lands in 0..n − 1, so write it once and stop worrying.
Why 10,007 buckets? Does it have to be a prime?
Close to the number of keys (≤ 10⁴ here) keeps each chain about one node long. A prime count spreads patterned keys better: with 1,000 buckets, keys 0, 1000, 2000, … all crowd into bucket 0; with 10,007 they scatter. Prime is a helpful habit, not a rule: java.util.HashMap uses powers of two and mixes the hash bits instead.
Is a hash set O(1) or O(n)? What should I say in the interview?
Both are true, for different cases. O(1) average: a good hash spreads keys so each chain is short. O(n) worst: if every key lands in one bucket, a lookup walks all n. Say "O(1) average, O(n) worst case" for your own chained set — that one line shows you understand how it works. (java.util.HashMap goes one step further: since Java 8 a long chain becomes a red-black tree, so its worst case is O(log n) for Comparable keys like Integer.)
Is the dummy node part of the list? Does size count it?
No. The dummy is a helper that sits before the real head; its val is never read. size counts only real nodes, and the first real node is dummy.next. So for [1, 2], size is 2 and get(0) returns dummy.next.val, which is 1.
If I start at the dummy and step index times, why do I land before position index?
Think of the dummy as position −1. One step reaches position 0, two steps reach position 1, so index steps reach position index − 1: the node just before the target. That is exactly the node you need to insert or delete. Want the node at index? Take one more step.
Why may addAtIndex use index == size, but get and deleteAtIndex may not?
Count the choices for [1, 2, 3]. There are 3 nodes to read or delete: indexes 0..2, so index < size. But there are 4 gaps to insert into (before 1, before 2, before 3, after 3): indexes 0..3, so index <= size. Inserting at index == size is just adding at the tail.
Is OOP even tested in DSA rounds?
Indirectly, all the time: every linked-list and tree problem is objects and references, every hash-based solution relies on equals/hashCode, and "Design X" problems are graded on clean classes as much as on Big-O.
10 · practice
Interview problems
Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.
- #705Design HashSetEasyAmazon
- #707Design Linked ListMediumMicrosoftAmazon
- #1603Design Parking SystemEasyAmazon
Also practise this here
Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.
- #622Design Circular QueueMediumin Queues
Another design class: an array plus two int fields (head, size), every method a guarded one-liner.
- #146LRU CacheMediumin Linked Lists
A
private static class Nodewith prev/next plus a HashMap: Design Linked List and Design HashSet in one problem. - #208Implement Trie (Prefix Tree)Mediumin Tries
static class TrieNode { TrieNode[] next = new TrieNode[26]; boolean end; }is the same nested-class habit. - #155Min StackMediumin Stacks
State design: decide what each stack entry must remember (the value and the minimum so far).
- #49Group AnagramsMediumin Arrays & Strings
Why the HashMap key is a
Stringand not achar[]: arrays use identity equals/hashCode, Strings compare contents.
11 · revise
The whole chapter on one screen
9 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.
- what it is
A class is the blueprint, an object one instance on the heap. Instance fields: one copy per object.
staticfields: one copy per class. - template
private static class Node { int val; Node next; Node(int v) { val = v; } }inside your class: no hidden outer reference. - use it when
"Design / Implement the X class": private fields for state, a constructor, one method per operation that checks, updates, returns.
- cost
HashMap lookups are O(1) only if
hashCodespreads the keys. A constant hashCode puts every key in one bucket: up to O(n) per lookup. - trap
Override
equalswithouthashCodeand HashSet/HashMap stop finding equal keys. Override both, from the same fields. - trap
staticcounters in a design class are shared by every object the judge creates. Per-object state = instance fields. - pro tip
Pairs, cells, composite keys:
record Cell(int r, int c) {}gives equals, hashCode and toString for free (Java 16+). - pro tip
A fixed set of options (directions, states) → an enum.
==is safe on enum constants; cachevalues(). - what it is
Interfaces say what (
Deque,Map,Comparable), classes say how. Declare by interface:Deque<Integer> st = new ArrayDeque<>();
12 · recall
Flip, remember, repeat
Why private static class Node rather than private class Node inside Solution?
Question 1: Why private static class Node rather than private class Node inside Solution?
13 · check yourself
Quick quiz
`class Lot { static int count; int free; }` Two Lot objects are created, and each runs `count++; free++;`. What does the first object see?
Inside `class Solution`, `class Node {…}` (no static) is created with `new Node(1)` in a static method. Result?
A class overrides equals (by fields) but not hashCode. After `set.add(new P(1, 2))`, what does `set.contains(new P(1, 2))` most likely return?
Best way to use a (row, col) pair as a HashMap key in Java 17?
Inserting node X after prev in a singly linked list. Which order is correct?
Design Parking System with `int[] free` indexed by carType: what does addCar cost?
Why cache `Direction.values()` in a static field?
14 · learn more
A second teacher, if you want one
- ▶ playlistObject Oriented Programming (OOP) in Java Course ↗Kunal Kushwaha · EnglishSeven lectures, from classes and constructors to interfaces, generics, lambdas and the Collections Framework. This is the OOP a DSA coder uses every day.
- ▶ videoJava OOPs in One Shot | Object Oriented Programming | Java Language | Placement Course ↗Apna College · HindiJava OOP in one sitting, in Hindi. Good for a quick revision of classes, objects and the four pillars before a round.
- § docsObject as a Superclass ↗Oracle · The Java Tutorials · EnglishThe official word on equals(), hashCode() and toString(). Your own class must follow this contract before it can work as a HashMap key.