Mastery
Mastery/Java/A. Object model, identity & memory
T2 · intermediate depth

Defensive copying, and why returning internal mutable state directly is a real bug

Returning a reference to internal mutable state (an array, a List, a Date) directly hands the caller the power to corrupt your object's internal invariants from outside -- often by complete accident. The fix costs almost nothing: return a copy instead. .clone() on arrays does exactly this and is one of the few places clone() is genuinely the idiomatic choice rather than a copy constructor.

java
class LeakyBox {
    private int[] data = {1, 2, 3};
    int[] getData() { return data; }   // hands out the ACTUAL internal array
}

class SafeBox {
    private int[] data = {1, 2, 3};
    int[] getData() { return data.clone(); }   // hands out an independent copy
}

LeakyBox leaky = new LeakyBox();
int[] leakedRef = leaky.getData();
leakedRef[0] = 999;   // "just modifying my local copy"... except it wasn't a copy
System.out.println("LeakyBox's internal state got corrupted from outside: " + java.util.Arrays.toString(leaky.getData()));

SafeBox safe = new SafeBox();
int[] copy = safe.getData();
copy[0] = 999;
System.out.println("SafeBox's internal state is untouched: " + java.util.Arrays.toString(safe.getData()));

Interview angle

"Your class has a getDates() method returning a List<Date> — what's wrong with it?" is a great practical question, since the bug (handing out a mutable reference to internal state) is realistic and common, and the fix (return a copy, or an unmodifiable view) is cheap and always worth doing for anything crossing a class boundary.

In the industry

Defensive copying is standard practice for any method returning internal mutable state from a class meant to protect its invariants — constructors too, when accepting mutable objects as parameters (copy on the way in, not just on the way out). It's one of the most common findings in security- and correctness-focused code review for public APIs, since the bug it prevents is invisible until something far away in the codebase mutates state it was never supposed to touch.