01

What Happens in Memory

Stack frames, heap objects and references: the picture behind every "why did my array change?" and every overflow bug.

beginner~50 min4 simulations

▶ guided lesson⚡ 1-minute summary↻ 13 recall cards

01 · why it exists

Why this exists, and the weight it carries

since1960

Call stack & heap

Edsger W. Dijkstra

Early programs gave each subroutine one fixed block of working memory. A subroutine could not be called again before it finished, because the new call would overwrite the old one's data. In "Recursive Programming" (1960), written for ALGOL 60 compilers, Dijkstra kept each call's data on a stack instead: push a fresh area on every call, drop it on return. The JVM works the same way today: every thread has a stack of frames, and objects live on a shared heap.

the problem it solvedLet methods call each other, and themselves, without overwriting each other's local variables, and free that memory the instant a call returns.

its jobs today

  • Every Java method call: the JVM pushes a frame with the parameters and locals, and pops it on return.
  • Every new: objects and arrays are allocated on the heap shared by all threads; frames hold only references to them.
  • Every exception's stack trace: it is a printout of the frames that were on the stack when things went wrong.

the weight it carries

Illustrative: the default 1 MB thread stack (64-bit Linux and macOS) ÷ about 100 bytes per small frame ≈ 10,000 nested calls. In our test (64-bit HotSpot, JDK 25 and 26 on Windows x64, -Xss1m), this chapter's recursive sumTo overflowed after about 8,900 calls interpreted (-Xint) and 11,000–41,000 with the JIT on, depending on how much of it had been compiled. Objects sit on the heap instead, which is sized separately (-Xmx) and is normally far larger.

without it

Without a frame per call, no method could call itself, so no tree DFS and no backtracking. Without a heap, nothing could outlive the method that made it: no linked lists, no HashMaps.

when · why · how

when
Whenever you ask: where does this variable live, and will it still exist after the method returns?
why
Stack memory is free to allocate and release (push and pop, O(1)) but small and short-lived. Heap memory is large and long-lived but cleaned by the GC. Recursion depth d costs O(d) stack.
how
Locals and parameters go in the frame; new puts the object on the heap and gives you a reference. For depth beyond about 10⁴, switch to a loop or an ArrayDeque, whose contents live on the heap.

02 · plain english

The idea in 30 seconds

Every method call gets a frame on the call stack for its parameters and local variables. The frame disappears when the method returns.

Every object you create with new, every array included, lives on the heap. A variable of object type holds only a reference: an arrow to it.

Java always copies the value into a parameter. For an object, that value is the arrow, so a method can change the object but never the caller's variable.

picture this

Desktop shortcuts in Windows: the shortcut (a reference in your frame) is tiny and points at a real folder (the object on the heap). Copy the shortcut and both copies open the same folder, so a file added through one shows up in the other. Deleting a shortcut never deletes the folder.

03 · remember

What you must know

  • Stack: one frame per running call, holding parameters, locals and partial results. Push on call, pop on return. Small: 1 MB per thread by default on 64-bit Linux and macOS.
  • Heap: every object and array (new). Shared, much bigger, and cleaned by the garbage collector once nothing can reach an object.
  • Primitive vs reference: an int variable holds the number itself. An int[], String or ListNode variable holds a reference (or null).
  • Always pass-by-value. swap(int a, int b) swaps copies. arr[0] = 99 inside a method changes the caller's array, because both arrows point at one array. arr = new int[5] inside the method changes nothing outside.
  • `==` compares what the variables hold: values for primitives, references for objects. Compare contents with equals (Arrays.equals for arrays).
  • `int` is 32 bits: about ±2.1·10⁹, and it wraps around silently. Widen to long before the operation, and write midpoints as lo + (hi - lo) / 2.
  • Arrays are objects: b = a copies the reference; a.clone() copies the contents; an int[][] is an array of references to separate rows.
  • Recursion depth = stack frames. Roughly 10⁴ simple frames fit in the default stack; deeper inputs need a loop or an explicit ArrayDeque.

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

Call stack and heap, frame by frame

Boxes on the left are frames and their variables, spheres on the right are heap objects, arrows are references. Watch the array outlive the method that made it.

loading simulation…
sim

Pass-by-value: swap fails, arr[i] = … works

swap gets copies of two numbers. swapInArray gets a copy of an arrow to the caller's array. reassign repoints only its own arrow.

loading simulation…
sim

int overflow in 32 bits: the (lo + hi) / 2 bug

Each row is an int's 32 bits; the leftmost is the sign bit. Watch a carry flip it, then three ways to compute a midpoint safely.

loading simulation…
sim

Autoboxing: the Integer cache trap

From the Java Toolkit chapter. Boxed numbers are heap objects too, so == compares arrows: true for 127, false for 128.

loading simulation…

05 · cost

Time & space, explained

What lives where, and for how long
Lives inFreed whenwhy
Local primitive (int i, long sum)the method's stack framethe method returnsThe value itself sits in the frame, so popping the frame frees it in O(1).
Local reference (int[] a, String s)the frame (only the arrow)the method returnsThe frame stores just the reference. The object it points at is separate.
Object or array (new int[n], new ListNode(5))the heapthe GC finds it unreachableAn object outlives the method that made it while any reference still reaches it.
Static field (static int[] memo)one copy for the whole classpractically neverWhatever a static field refers to stays alive for the whole run, across test cases.
Recursion of depth dd frames on the stackeach call returnsStack space is O(d) even when each call does O(1) work.
Everyday memory operations and their cost
TimeExtra memorywhy
Pass an array or object to a methodO(1)O(1)Only the reference is copied, never the elements.
int[] b = a;O(1)O(1)A second arrow to the same array.
a.clone() / Arrays.copyOf(a, n)O(n)O(n)A new array; every element is copied.
new int[n]O(n)O(n)The JVM fills all n slots with 0 before you see the array.
new ArrayList<>(path) snapshotO(k)O(k)k = the path's current length, paid once per saved result.
s.equals(t) vs s == tO(n) vs O(1)O(1)equals compares characters (stopping at the first difference); == compares only references.
Recursion to depth d(the calls' own work)O(d) stackOne frame per call that is still waiting for its child to return.

06 · java

Raw vs Collections Framework

Stack frames and heap objects

javaframes, a heap array and an aliasjava.util.*
static int[] makeArray(int n) {    int[] arr = new int[n];                  // the array object lives on the heap    for (int i = 0; i < n; i++) {        arr[i] = (i + 1) * 10;    }    return arr;                              // hands back the reference, not a copy} static int sum(int[] a) {    int total = 0;                           // a primitive: stored inside sum's frame    for (int x : a) {        total += x;    }    return total;                            // sum's frame is thrown away after this} static int run(int n) {    int[] data = makeArray(n);    int[] alias = data;                      // copies the arrow, not the array    alias[0] = 99;                           // so data[0] is 99 now too    return sum(data);}
  • Each call pushes a frame holding its parameters and locals; return pops it. That is why makeArray's i no longer exists after it returns.
  • new int[n] creates the array on the heap and hands you a reference. Returning it returns the reference; no elements are copied.
  • int[] alias = data makes a second arrow, not a second array. A write through either name is seen through both.
  • When the last arrow disappears the array is garbage. The GC reclaims it later; Java has no free or delete.

Pass-by-value: what a method can and cannot change

javaswap, swapInArray, reassignjava.util.*
static void swap(int a, int b) {             // a and b are COPIES of the caller's ints    int t = a;    a = b;    b = t;                                   // only the copies are swapped} static void swapInArray(int[] arr, int i, int j) {   // arr is a copy of the REFERENCE    int t = arr[i];    arr[i] = arr[j];    arr[j] = t;                              // writes into the caller's array object} static void reassign(int[] arr) {    arr = new int[] {0, 0};                  // points the local copy at a NEW array    arr[0] = -1;                             // the caller never sees this array} static String demo(int x, int y) {    swap(x, y);    int[] nums = {x, y};    swapInArray(nums, 0, 1);    reassign(nums);    return x + " " + y + " " + Arrays.toString(nums);}
  • Every argument is copied into a new parameter. For an int the copy is the number; for an array, list or node it is the reference.
  • Through a copied reference you can change the contents: arr[i] = …, list.add(…), node.next = …. The caller sees it.
  • Assigning the parameter itself (arr = new int[2], head = head.next) only moves the local arrow. The caller's variable does not move.
  • Backtracking classic: result.add(path) stores the one path object again and again. Store a copy with new ArrayList<>(path): O(k) per saved result.

Sharing a counter across recursive calls

javalost copy vs shared array vs return valuejava.util.*
static void countBuggy(int n, int count) {   // every frame gets its own copy of count    if (n == 0) return;    count++;                                 // lost as soon as this frame returns    countBuggy(n - 1, count);} static void countInto(int n, int[] count) {  // every frame shares ONE array on the heap    if (n == 0) return;    count[0]++;    countInto(n - 1, count);} static int countReturned(int n) {            // cleanest: return the answer up the stack    return n == 0 ? 0 : 1 + countReturned(n - 1);}
  • An int count parameter is copied into every frame, so count++ vanishes when that frame returns.
  • Three versions that work: return the value up the stack (cleanest), share an int[] {0} across frames, or use a field.
  • A static field keeps its value between calls in the same run, so a count from one test case can leak into the next. Reset it at the start, or prefer an instance field.

== vs equals, and the String pool

javastrings and arraysjava.util.*
static boolean[] stringChecks() {    String a = "rahul";                      // a literal: stored once in the String pool    String b = "rahul";                      // the same literal: the same pooled object    String c = new StringBuilder("ra").append("hul").toString();  // built at run time: a new object    return new boolean[] {        a == b,                              // true:  same object (thanks to the pool)        a == c,                              // false: two objects, same text        a.equals(c),                         // true:  compares the characters    };} static boolean[] arrayChecks() {    int[] x = {1, 2, 3};    int[] y = {1, 2, 3};    return new boolean[] {        x == y,                              // false: two array objects        x.equals(y),                         // false: arrays keep Object.equals, which is ==        Arrays.equals(x, y),                 // true:  element by element    };}
  • == compares what the variables hold: values for primitives, references for objects. It asks "same object?", not "same contents?".
  • String literals are interned (stored once), so "rahul" == "rahul" is true. Strings built at run time (substring, StringBuilder, input) are new objects.
  • Arrays do not override equals, so x.equals(y) is still identity. Use Arrays.equals for 1D and Arrays.deepEquals for 2D.
  • Boxed numbers have the same trap: the Integer cache sim above shows why == works for 127 and fails for 128.

Arrays are objects: alias vs copy

javaalias vs clonejava.util.*
static int[][] aliasVsCopy() {    int[] original = {1, 2, 3};    int[] alias = original;                  // same array, two names    int[] copy = original.clone();           // a new array with the same values (Arrays.copyOf works too)    alias[0] = 100;                          // visible through original    copy[1] = 200;                           // original is not affected    return new int[][] {original, copy};     // [[100, 2, 3], [1, 200, 3]]}
  • An array is a heap object with a fixed length, filled with 0, false or null when created.
  • int[] alias = original copies the reference: one array, two names. clone() or Arrays.copyOf builds a second array.
  • Copy before you modify when the caller still needs the original (for example, sorting a copy to keep the original indices).

2D arrays are arrays of row references

javashared rows vs separate rows vs a deep copyjava.util.*
static int[][] sharedRowsBug(int rows, int cols) {    int[][] grid = new int[rows][];          // an array of row REFERENCES, all null for now    int[] row = new int[cols];    Arrays.fill(grid, row);                  // BUG: every grid[r] points at the SAME row    grid[0][0] = 7;                          // so this "changes" every row    return grid;} static int[][] separateRows(int rows, int cols) {    int[][] grid = new int[rows][cols];      // rows separate row arrays, all zeros    grid[0][0] = 7;    return grid;} static int[][] deepCopy(int[][] grid) {    int[][] copy = new int[grid.length][];    for (int r = 0; r < grid.length; r++) {        copy[r] = grid[r].clone();           // grid.clone() alone would copy only the row references    }    return copy;}
  • new int[r][c] is one array of r references, each pointing at its own int[c] row.
  • clone() and Arrays.copyOf copy one level only. For a grid you will modify (backtracking, simulations), clone every row.
  • Arrays.fill(grid, row) puts the SAME row object in every slot. The bug stays hidden until the first write.

Primitive sizes, and when to reach for long

javathe limits, long sums, cast-before-multiplyjava.util.*
static long[] limits() {    return new long[] {        Byte.MAX_VALUE,                      // 127                  (8 bits)        Short.MAX_VALUE,                     // 32767                (16 bits)        Character.MAX_VALUE,                 // 65535                (16 bits, unsigned: a char is a number)        Integer.MAX_VALUE,                   // 2147483647, ~2.1e9   (32 bits)        Long.MAX_VALUE,                      // 9223372036854775807, ~9.2e18 (64 bits)    };} static long sumAll(int[] nums) {    long total = 0;                          // 10^5 values of 10^9 each = 10^14: needs long    for (int x : nums) total += x;    return total;} static long areaWrong(int w, int h) {    return (long) (w * h);                   // BUG: the int multiply overflowed before the cast} static long area(int w, int h) {    return (long) w * h;                     // cast first, then multiply in 64 bits} static int checkedAdd(int a, int b) {    return Math.addExact(a, b);              // throws ArithmeticException instead of wrapping}
  • The JLS fixes the sizes on every machine: byte 8 bits, short 16, char 16 (unsigned), int 32, long 64.
  • int runs from −2,147,483,648 to 2,147,483,647 (≈ ±2.1·10⁹). long reaches ≈ ±9.2·10¹⁸.
  • Reading constraints: values up to 10⁹ fit in an int, but their sums and products usually do not.
  • Cast before the operation: (long) a * b. Writing (long) (a * b) converts a number that is already wrong.
  • Math.addExact and Math.multiplyExact throw ArithmeticException on overflow, which is handy while debugging.
  • double keeps 53 bits of precision: fine for averages, wrong for exact big integers or money. Store money as long paise.

Overflow and the (lo + hi) / 2 bug

javawrap-around and three midpoint fixesjava.util.*
static int wrapDemo() {    return Integer.MAX_VALUE + 1;            // wraps around to Integer.MIN_VALUE, silently} static int midBroken(int lo, int hi) {    return (lo + hi) / 2;                    // lo + hi can pass 2^31 - 1 and turn negative} static int midSafe(int lo, int hi) {    return lo + (hi - lo) / 2;               // hi - lo never overflows when 0 <= lo <= hi} static int midShift(int lo, int hi) {    return (lo + hi) >>> 1;                  // unsigned shift reads the wrapped bits correctly} static long sumAsLong(int lo, int hi) {    return (long) lo + hi;                   // widen BEFORE adding: 64 bits hold any int sum}
  • int arithmetic wraps silently: Integer.MAX_VALUE + 1 == Integer.MIN_VALUE. No exception, just a wrong number.
  • Joshua Bloch reported the (low + high) / 2 bug in the JDK's own Arrays.binarySearch in 2006. Today's JDK computes (low + high) >>> 1.
  • lo + (hi - lo) / 2 is the version to write in interviews: it is safe whenever 0 ≤ lo ≤ hi, and easy to explain.

Autoboxing: what Integer costs

javaprimitive vs boxed, array vs mapjava.util.*
static long sumPrimitive(int n) {    long total = 0;                          // 8 bytes inside the frame, no objects at all    for (int i = 0; i < n; i++) total += i;    return total;} static Long sumBoxed(int n) {    Long total = 0L;                         // a Long OBJECT: each += unboxes, adds, then boxes a new Long    for (int i = 0; i < n; i++) total += i;    return total;} static int[] countsArray(int[] values, int maxValue) {    int[] counts = new int[maxValue + 1];    // 4 bytes per counter, one object in total    for (int v : values) counts[v]++;    return counts;} static Map<Integer, Integer> countsMap(int[] values) {    Map<Integer, Integer> counts = new HashMap<>();  // boxed keys, boxed values, one entry object each    for (int v : values) counts.merge(v, 1, Integer::sum);    return counts;}
  • An int is 4 bytes stored in place. An Integer is a separate heap object with its own header, reached through a reference: several times the memory.
  • Long total = 0L; total += i; unboxes, adds and boxes a fresh Long on every iteration. Use long.
  • For counts of small non-negative keys, int[] beats HashMap<Integer, Integer>: one object instead of a boxed key, a boxed value and an entry per key.
  • The == trap on boxed values is covered in the Java Toolkit chapter (the Integer cache).

Recursion depth and StackOverflowError

javaone frame per level vs one reused framejava.util.*
static long sumTo(int n) {    if (n == 0) return 0;                    // base case    return n + sumTo(n - 1);                 // every call waits on the stack: depth n} static long sumToLoop(int n) {    long total = 0;                          // one frame, reused n times    for (int i = 1; i <= n; i++) {        total += i;    }    return total;}
  • Each recursive call adds a frame until the base case returns. Depth n costs O(n) stack space even when each call does O(1) work.
  • The default thread stack is 1 MB on 64-bit Linux and macOS (-Xss). In our test (64-bit HotSpot, JDK 25/26 on Windows x64, -Xss1m) this sumTo overflowed after about 8,900 calls interpreted (-Xint) and 11,000–41,000 with the JIT on; your numbers will differ.
  • sumToLoop does the same work in one frame. Recursion that just walks a line (a list, a counter) → write a loop. Branching recursion on deep inputs → an explicit ArrayDeque stack (see the Recursion chapter).
  • Java does not optimise tail calls, so even tail-recursive code grows the stack.

Garbage collection: the one habit DSA code needs

javanull out popped slotsjava.util.*
class ObjectStack<T> {    private Object[] items = new Object[4];    private int size = 0;     void push(T item) {        if (size == items.length) items = Arrays.copyOf(items, size * 2);  // old array becomes garbage        items[size++] = item;    }     @SuppressWarnings("unchecked")    T pop() {        T top = (T) items[--size];        items[size] = null;                  // forget it, so the GC can reclaim the object        return top;    }     int size() {        return size;    }}
  • The GC frees objects that nothing can reach. Reaching starts from live frames and static fields.
  • Your own array-backed stack keeps popped objects alive through old slots unless you set them to null. ArrayList and ArrayDeque already clear removed slots.
  • Growing an array (Arrays.copyOf) turns the old array into garbage. That is fine: the GC handles it.
  • Never call System.gc() in a solution. It only suggests a collection, and a full collection is slow.
Java API you will actually use
calldoescost
Integer.MAX_VALUE / Integer.MIN_VALUE2³¹ − 1 and −2³¹: the int limitsO(1)
Long.MAX_VALUE2⁶³ − 1 ≈ 9.2·10¹⁸O(1)
(long) a * bMultiply in 64 bits (cast first)O(1)
lo + (hi - lo) / 2 or (lo + hi) >>> 1Overflow-safe midpointO(1)
Math.addExact / multiplyExact / toIntExactThrow ArithmeticException instead of wrappingO(1)
a.clone() / Arrays.copyOf(a, n)Shallow copy of an arrayO(n)
Arrays.equals(a, b) / Arrays.deepEquals(g, h)Compare arrays element by element (deep for 2D)O(n)
s.equals(t) / Objects.equals(a, b)Compare contents (the second is null-safe)O(n) for strings
new ArrayList<>(list)Snapshot a list's current contentsO(n)

A helper must change the caller's numbers

→ Return them (an int[] or a record), or change the contents of an array or object you passed

Primitive parameters are copies; contents behind a reference are shared.

Count something across recursive calls

→ Return the count up the stack, or a field / int[1]

An int parameter is copied per frame, so increments are lost.

Save a list that will keep changing

→ new ArrayList<>(list)

Storing the reference stores the future, mutated list.

A copy of an array you can change freely

→ a.clone() / Arrays.copyOf (row by row for 2D)

b = a copies only the reference.

Values, sums or products that can pass 2.1·10⁹

→ long, cast before the operation

int wraps silently; long holds up to 9.2·10¹⁸.

Compare strings or boxed numbers

→ equals / Objects.equals

== compares identity, not contents.

Recursion deeper than about 10⁴ levels

→ A loop, or an ArrayDeque as an explicit stack

Frames are limited by the thread stack; a deque's contents live on the heap.

07 · recognise

Spot the pattern

Constraints like nums[i] ≤ 10⁹ and you add or multiply values

long sums and (long) a * b

Binary search on indices or on an answer range near 2³¹

lo + (hi - lo) / 2, never (lo + hi) / 2

"Modify in place" and the method returns void

Change the array's contents through the reference you were given

A helper needs to give back two numbers

Return an int[] or a record; int parameters are copies

Collecting paths or subsets while backtracking

Save new ArrayList<>(path), not path

Recursion depth can reach 10⁴–10⁵ (long list, skewed tree, path graph)

Stack overflow risk: loop or explicit ArrayDeque

Comparing strings, Integers or arrays

equals / Arrays.equals, never ==

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Expecting swap(int a, int b) or count++ inside a method to change the caller's variables.
  • Reassigning a parameter (arr = new int[n], head = head.next) and expecting the caller to see the new object.
  • Comparing Strings, Integers or arrays with ==: it compares references.
  • result.add(path) in backtracking without copying: every saved entry is the same list.
  • int mid = (lo + hi) / 2 on large ranges, an int sum over 10⁵ values of 10⁹, or (long) (a * b).
  • Arrays.fill(grid, row) or grid.clone() and believing the rows are independent.
  • Recursing 10⁵ levels deep (a long linked list, a skewed tree) and getting StackOverflowError.
  • Long sum = 0L or Integer count in a hot loop: a new box on every update.
  • Keeping references you no longer need (popped slots, ever-growing caches): the GC cannot free them.

tricks

  • Draw it: boxes for frames, circles for objects, arrows for references. Two arrows on one circle means a change through either is seen by both.
  • Before submitting, look at every int that is summed or multiplied. Can it pass 2.1·10⁹? If unsure, make it long.
  • Say it in the interview: "I pass the array reference, so the helper sorts it in place with O(1) extra space."
  • When a helper must produce two numbers, return an int[] or a record instead of fighting pass-by-value.
  • If recursion depth can reach about 10⁴, mention the stack risk yourself and offer the iterative version.
  • Use Objects.equals(a, b) when either side might be null.
  • Bas itna yaad rakho: primitives are copied, objects are shared through arrows.

09 · why & how

Questions you might be too shy to ask

Is Java pass-by-reference for objects?

No, and interviewers love this question. Java copies every argument's value. For an object variable that value is a reference, so the method gets a second arrow to the same object: it can change the object, but it cannot make the caller's variable point somewhere else. Real pass-by-reference (C++ int&) would allow that; Java never does.

How do I tell if a line changes the object or only moves my own arrow?

Look at the left side of the =. If it is the bare variable (arr = …, head = head.next), only your arrow moves; the caller sees nothing. If it goes through the variable (arr[i] = …, node.next = …, list.add(…)), the shared object changes and everyone sees it. Bas itna dekho: a dot or a [ ] before the change means the object itself is touched.

If objects are shared, why can't my method change a String I pass in?

Because a String object can never change: it is immutable. s = s + "x" inside the method builds a new String and moves only the local arrow. The caller's s still points at the old text. To give back a new string, return it.

What exactly is null, and why do I get NullPointerException?

null is a reference that points at no object. The variable exists, but there is nothing at the end of the arrow. Using it as an object (node.next, arr.length, s.equals(t)) throws NullPointerException. In linked lists and trees, check node != null before you touch node.next or node.left.

My code passes Run but fails Submit, and I used a static variable. Is that related?

Very likely. A judge may run many test cases in one program run (for LeetCode this is community-reported, not documented), and a static field keeps its value between them. Then a count or memo left over from test 1 corrupts test 2. Reset it at the start of the method, or make it a normal (instance) field or a local variable.

If the stack is small, can I put my variables on the heap instead?

Java decides for you: locals and parameters always live in the frame, objects always live on the heap. What you control is depth. A loop reuses one frame; recursion adds one per level. For very deep work, keep your own ArrayDeque, whose contents are heap objects.

Do I ever need to free memory in Java?

There is no free or delete. The GC reclaims an object once nothing reachable refers to it. Your only job is to stop referring to things you are done with. That rarely matters on LeetCode. It matters in long-running programs, like a cache that never drops old entries or an array slot that still points at a removed object.

Why does "abc" == "abc" print true if == is wrong for strings?

Both literals are the same pooled object: the JLS interns string literals. Strings you build at run time are new objects, so == turns false on exactly the hidden tests that build strings. equals is right in both cases.

Do I have to fill a new array with zeros? And why does my local int x; give a compile error?

new int[n] is already all 0 (false for boolean[], null for object arrays), so no fill loop is needed. Fields get the same defaults. Local variables do not: int x; x++; fails with "variable x might not have been initialized". Give every local a value before you read it.

I wrote long ans = a * b; with int a and b, and the answer is still wrong. Why?

Java computes the right side first, using the types of a and b. Both are int, so the multiply happens in 32 bits and wraps. Only the already-wrong result is then widened to long. Fix: long ans = (long) a * b; so the multiply itself runs in 64 bits.

How do I decide between int and long from the constraints?

Estimate the biggest value your variable can ever hold. Example: n ≤ 10⁵ values, each ≤ 10⁹, so the sum can reach 10⁵ × 10⁹ = 10¹⁴. That is far above 2.1·10⁹, so the sum needs long. Indices and counts up to 10⁵ are safe in int.

Why does Java wrap around instead of throwing on overflow?

Speed and simplicity. The JLS defines int arithmetic as 32-bit two's complement and says the integer operators never signal overflow, which matches what the CPU does natively. If you want a check, Math.addExact and Math.multiplyExact throw ArithmeticException.

My recursive solution passes small tests but crashes on big ones. Why?

Check the depth. A linked list or a skewed tree with 10⁵ nodes means 10⁵ waiting frames. A typical default stack (often 1 MB) holds only roughly 10⁴ simple ones; the exact limit depends on the JVM, its settings and how big each frame is. Make it iterative, or keep the recursion but bound its depth (balanced trees are only about log₂ n deep).

Does the recursion stack count in space complexity?

Yes. Every waiting call holds a frame, so recursion of depth d uses O(d) extra space even if you created no array. Example: recursive DFS on a skewed tree of n nodes is O(n) space, on a balanced tree O(log n). Say it in the interview, because interviewers check whether you noticed.

10 · practice

Interview problems

Practise this chapter on these

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #704
    Binary Search
    Easyin Binary Search

    Write the midpoint as lo + (hi - lo) / 2 and say why out loud: lo + hi can overflow.

  2. #69
    Sqrt(x)
    Easyin Big-O

    mid * mid overflows an int once mid passes 46,340. Square in long, or compare mid <= x / mid.

  3. #50
    Pow(x, n)
    Mediumin Recursion

    n = Integer.MIN_VALUE has no positive int partner, so -n overflows. Widen to long first.

  4. #283
    Move Zeroes
    Easyin Two Pointers

    The method returns void, yet the caller sees the result: the array is changed through the copied reference.

  5. #78
    Subsets
    Mediumin Backtracking

    The snapshot trap in its natural habitat: add new ArrayList<>(path), never path itself.

  6. #206
    Reverse Linked List
    Easyin Linked Lists

    Pure reference rewiring with three local arrows and no new nodes. The recursive version uses one stack frame per node.

  7. #104
    Maximum Depth of Binary Tree
    Easyin Tree Recursion

    Recursion depth equals tree height: a skewed tree of 10⁴ nodes means 10⁴ frames.

11 · revise

The whole chapter on one screen

10 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    Each call gets a frame on the stack (parameters + locals). Every new object or array lives on the heap. Variables hold references: arrows to objects.

  2. what it is

    Java is always pass-by-value. For an object, the value copied is the reference: a method can change the object, never the caller's variable.

  3. use it when

    Use long when sums or products can pass ~2.1·10⁹: many big values, a * b above 46,340, prefix sums, mod 10⁹+7 math.

  4. template

    Safe midpoint: int mid = lo + (hi - lo) / 2; or int mid = (lo + hi) >>> 1;

  5. cost

    Passing an array or list to a method is O(1): one reference is copied. Copying it (clone, new ArrayList<>(list)) is O(n).

  6. cost

    Recursion depth d uses O(d) stack. A default 1 MB stack holds roughly 10⁴ simple frames, then StackOverflowError.

  7. trap

    == on objects compares references. Use equals for String/Integer and Arrays.equals for arrays. Literals only look safe because they are pooled.

  8. trap

    result.add(path) stores the same list every time. Snapshot it: result.add(new ArrayList<>(path)).

  9. trap

    (long) (a * b) is too late: the int multiply already wrapped. Write (long) a * b.

  10. pro tip

    Garbage = unreachable. In your own array-backed stack, set items[size] = null after a pop so the GC can free the object.

12 · recall

Flip, remember, repeat

card 1 / 13
recall

Where do a method's local int and its int[] arr = new int[n] live?

tap or Space to flip ↻

Question 1: Where do a method's local int and its int[] arr = new int[n] live?

13 · check yourself

Quick quiz

  1. `int x = 1, y = 2; swap(x, y);` where swap exchanges its two int parameters. What are x and y afterwards?

  2. `void f(int[] a) { a[0] = 7; a = new int[] {9}; }` is called with `arr = {1, 2}`. What is arr afterwards?

  3. `String a = "hi"; String b = new StringBuilder("h").append("i").toString();` What are `a == b` and `a.equals(b)`?

  4. lo = 1,500,000,000 and hi = 2,000,000,000 are ints. What does `(lo + hi) / 2` give?

  5. Which of these lives on the heap?

  6. Which line correctly saves the current backtracking path?

  7. Which expression multiplies the ints w and h without overflow?