☕ Java

Passing Arrays to Methods

When an array is passed to a method in Java, the method receives a copy of the reference — not a copy of the array's data. Both the caller and the method hold references that point to the same array object on the heap. This means the method can read and modify the array's elements, and those modifications are visible to the caller after the method returns. Understanding this reference semantics is essential for correctly designing methods that work with arrays.

How Array Parameters Work — Reference Semantics

Java always passes arguments by value. For primitive types, this means the method receives a copy of the value — changing the parameter inside the method has no effect on the caller's variable. For reference types, including arrays, this means the method receives a copy of the reference — a copy of the memory address pointing to the array object. This is often described as "pass by value of the reference." The consequence is that both the caller's variable and the method's parameter hold references to the same array object. When the method modifies an element (array[0] = 99), it is modifying the shared array object, and the change is immediately visible through the caller's variable. This is the same behaviour you would expect from pass-by-reference in other languages — modifications persist after the method returns. However, reassigning the parameter variable inside the method (array = new int[10]) has no effect on the caller. The parameter variable is a local copy of the reference. Reassigning it makes the local parameter point to a new array, but the caller's variable still points to the original array — the original is unchanged. This distinction between modifying elements of the array (visible to caller) and reassigning the array variable (not visible to caller) is the crucial subtlety of Java's pass-by-value-of-reference semantics. Designing methods that accept arrays requires being explicit about whether the method should modify the caller's array or work on its own copy. Methods that modify their array parameter are called mutating methods and should clearly document this in their contract. Methods that should not modify the caller's array should either accept the array as read-only by convention and document this, or work on a defensive copy made at the start of the method.
Java
// ── Method receives a copy of the reference — same underlying array: ──
public static void addTen(int[] array) {
    for (int i = 0; i < array.length; i++) {
        array[i] += 10;         // modifies the shared array object
    }
}

int[] values = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(values));  // [1, 2, 3, 4, 5]
addTen(values);
System.out.println(Arrays.toString(values));  // [11, 12, 13, 14, 15]
// Changes visible in caller — method modified the shared array.

// ── Reassigning the parameter does NOT affect the caller: ────────────
public static void tryToReplace(int[] array) {
    array = new int[]{99, 98, 97};  // local parameter now points elsewhere
    // The caller's array is unaffected — we only changed the local copy
    System.out.println("Inside method: " + Arrays.toString(array));
}

int[] data = {1, 2, 3};
tryToReplace(data);
System.out.println("After method:  " + Arrays.toString(data));
// Inside method: [99, 98, 97]  — local copy points to new array
// After method:  [1, 2, 3]     — caller's array unchanged

// ── Visual model: ─────────────────────────────────────────────────────
//
// Before call:   caller.values ──────────────────────▶ [1,2,3,4,5]
//
// During call:   caller.values ──────────────────────▶ [1,2,3,4,5]
//                method.array  ─────────────────────▲
//                                 (same object)
//
// After addTen:  caller.values ──────────────────────▶ [11,12,13,14,15]
//                (visible because they share the same array)
//
// After tryToReplace:
//                caller.data   ──────────────────────▶ [1,2,3] (unchanged)
//                method.array  ───▶ [99,98,97]  (new object — local only)

Mutating vs Non-Mutating Methods

Methods that accept arrays fall into two categories based on whether they modify the array they receive. Mutating methods intentionally change the elements of the array parameter, and these changes are the method's entire purpose — sorting, filling, doubling elements, reversing. Non-mutating methods read the array but should not change it — computing the sum, finding the maximum, searching for a value. The distinction matters for correctness, API design, and documentation. A method named computeSum should not sort the array as a side effect. A method named sortAscending is expected to modify the array. When a method's purpose does not include modifying the array but it modifies the array anyway, that is an unintended side effect — one of the most common sources of hard-to-find bugs. The Java language does not provide a way to mark array parameters as read-only (unlike C's const pointer). The compiler will not prevent a method from modifying an array it receives. The only enforcement is through discipline and documentation. For critical APIs where the caller must guarantee the array will not be modified, the method should take a defensive copy at the start of its body and work only on the copy. This is the pattern used by security-sensitive and library code where callers cannot be trusted. The alternative — having the caller make the copy before passing — is also valid but places the burden on every caller rather than on the method that knows its own contract. Library methods that might modify their input should generally take the copy themselves and document that they work on a copy. Methods that document "this method modifies the passed array" place the responsibility on the caller to understand and accept this.
Java
// ── Mutating method — documented to modify the array: ────────────────
/**
 * Sorts the array in ascending order.
 * This method modifies the given array in place.
 * @param array the array to sort — WILL BE MODIFIED
 */
public static void bubbleSort(int[] array) {
    for (int i = 0; i < array.length - 1; i++) {
        for (int j = 0; j < array.length - i - 1; j++) {
            if (array[j] > array[j + 1]) {
                int temp    = array[j];
                array[j]    = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

int[] nums = {5, 2, 8, 1, 9};
bubbleSort(nums);
System.out.println(Arrays.toString(nums));  // [1, 2, 5, 8, 9] — modified

// ── Non-mutating method — reads but does not modify: ──────────────────
/**
 * Returns the sum of all elements.
 * The array is not modified.
 */
public static long sum(int[] array) {
    long total = 0;
    for (int value : array) {
        total += value;
    }
    return total;   // returns result, array unchanged
}

int[] data = {10, 20, 30, 40, 50};
System.out.println(sum(data));              // 150
System.out.println(Arrays.toString(data)); // [10, 20, 30, 40, 50] — unchanged

// ── Defensive copy — method that works on a copy internally: ──────────
/**
 * Returns the array sorted in ascending order.
 * The original array is NOT modified — this method works on a copy.
 */
public static int[] sortedCopy(int[] array) {
    int[] copy = Arrays.copyOf(array, array.length);  // defensive copy
    Arrays.sort(copy);
    return copy;    // return the sorted copy, original untouched
}

int[] original = {5, 2, 8, 1, 9};
int[] sorted   = sortedCopy(original);
System.out.println(Arrays.toString(original));  // [5, 2, 8, 1, 9] — unchanged
System.out.println(Arrays.toString(sorted));    // [1, 2, 5, 8, 9]

// ── Caller makes the copy when they need to preserve their data: ───────
int[] myData = {5, 2, 8, 1, 9};
bubbleSort(Arrays.copyOf(myData, myData.length));  // pass copy to mutating method
System.out.println(Arrays.toString(myData));        // [5, 2, 8, 1, 9] — safe

Passing 2D Arrays and Partial Arrays

Two-dimensional arrays are passed to methods using the same reference semantics as 1D arrays. The method receives a reference to the outer array, through which it can access all inner arrays and their elements. Because the outer array holds references to inner arrays, and those inner arrays are shared objects, modifications to elements of the inner arrays are visible to the caller. Replacing an entire inner array (matrix[0] = new int[]{...}) replaces the outer array's reference and is also visible to the caller — unlike replacing the outer array reference itself, which is local to the method. Passing a portion of an array to a method is not directly supported in Java — there is no "array slice" type that refers to a sub-range of an existing array without copying. The common approaches are: pass the full array along with start and end index parameters (letting the method operate only on the specified range), or pass a copy of the desired range using Arrays.copyOfRange() before the call. The first approach is more efficient (no copy) but requires the method signature to include the range parameters. The second is cleaner for the method signature but allocates a new array.
Java
// ── Passing a 2D array — modifications visible to caller: ────────────
public static void multiplyAllByTwo(int[][] matrix) {
    for (int r = 0; r < matrix.length; r++) {
        for (int c = 0; c < matrix[r].length; c++) {
            matrix[r][c] *= 2;         // modifies shared inner arrays
        }
    }
}

int[][] grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
multiplyAllByTwo(grid);
System.out.println(Arrays.deepToString(grid));
// [[2, 4, 6], [8, 10, 12], [14, 16, 18]]

// ── Replacing an inner row — also visible to caller: ─────────────────
public static void zeroOutFirstRow(int[][] matrix) {
    matrix[0] = new int[matrix[0].length];  // replace inner array
    // new int[n] is filled with zeros by default
}

int[][] m = {{1, 2, 3}, {4, 5, 6}};
zeroOutFirstRow(m);
System.out.println(Arrays.deepToString(m));  // [[0, 0, 0], [4, 5, 6]]

// ── Partial array processing — pass index range parameters: ───────────
/**
 * Fills array[from] through array[to-1] with the given value.
 * Operates on the specified range of the caller's array.
 */
public static void fillRange(int[] array, int from, int to, int value) {
    for (int i = from; i < to; i++) {
        array[i] = value;
    }
}

int[] arr = {1, 2, 3, 4, 5, 6, 7};
fillRange(arr, 2, 5, 0);    // fill indices 2,3,4 with 0
System.out.println(Arrays.toString(arr));  // [1, 2, 0, 0, 0, 6, 7]

// ── Partial array via copy — clean method signature: ──────────────────
/**
 * Sorts the given array in place using standard sort.
 */
public static void sort(int[] array) {
    Arrays.sort(array);
}

int[] data = {5, 3, 8, 1, 6, 2};
// Sort only indices 1 through 4:
int[] subArray = Arrays.copyOfRange(data, 1, 5);    // copy the range
sort(subArray);                                      // sort the copy
System.arraycopy(subArray, 0, data, 1, subArray.length);  // put back
System.out.println(Arrays.toString(data));  // [5, 1, 3, 6, 8, 2]

Related Topics in Arrays

Array Basics
An array in Java is a fixed-size, ordered collection of elements of the same type stored in a contiguous block of memory. Once created, the size of an array cannot change. Arrays are the simplest and most fundamental data structure in Java — they underlie many higher-level collections and provide direct, indexed access to elements in constant time O(1). Every array in Java is an object, inherits from java.lang.Object, and can hold either primitives or references.
One-Dimensional Array
A one-dimensional array is a linear sequence of elements of the same type, accessed by a single integer index. It is the simplest array form in Java and the foundation for understanding multidimensional arrays and collection classes. One-dimensional arrays are used to store and process lists of values — student grades, product prices, daily temperatures, character sequences — any time you need to work with a fixed-size ordered collection of homogeneous data.
Two-Dimensional Array
A two-dimensional array in Java is an array of arrays — each element of the outer array is itself a one-dimensional array. It is used to represent tabular data with rows and columns: matrices, game boards, spreadsheet grids, pixel buffers, and any data that is naturally organised in two dimensions. In Java, a 2D array is declared with two sets of brackets and accessed with two indices: array[row][column].
Multidimensional Array
A multidimensional array in Java is an array with more than two dimensions — an array of arrays of arrays, and so on. While two-dimensional arrays suffice for most programming tasks, three-dimensional arrays are used for spatial data (x, y, z coordinates of a 3D grid), volumetric data (layers, rows, columns), RGB pixel buffers, and scientific computations involving tensors. Java supports arbitrary dimensionality, though arrays beyond three dimensions are rare in practice.