skip to content

In Java, what is a two-dimensional array really, and why does Java have no true rectangular multidimensional arrays?

level: juniorimportance: must knowfreq 70%

answer

  1. int[][] = array of int[] references
  2. a.length = rows, a[i].length = that row
  3. two dereferences per a[i][j]
  4. rows are independent heap objects
  5. no contiguous rows×cols block like C

basics

~20 s

A Java 2D array is an array whose elements are themselves arrays (an array of arrays). Java has no single block of rows-by-columns; each row is a separate array object, so rows can even have different lengths.

solid answer

~40 s

Java has no genuine rectangular multidimensional array type. What looks like a 2D array, int[][], is really a one-dimensional array whose elements are references to other one-dimensional int[] arrays. So a[0] and a[1] are independent array objects, possibly at unrelated places in memory and possibly of different lengths. a.length gives the number of rows; a[i].length gives that particular row's length. This array-of-arrays model is why jagged arrays exist for free and why you index with a[i][j] (first pick the row reference, then index into it). It contrasts with languages like C or Fortran that store a true contiguous N-dimensional block. The trade-off: flexibility and easy sub-array sharing, but an extra pointer dereference per dimension and worse cache locality.

go deeper

for a junior

Knows a 2D array is an array of arrays and can use a[i][j], a.length, a[i].length correctly.

for a middle

Explains the two-dereference indexing, that rows are independent objects, and that rows can differ in length.

for a senior

Discusses cache-locality and performance implications versus a flattened 1D layout and when to choose each.

for a principal

Frames the array-of-arrays decision as a language design trade-off (flexibility/aliasing vs. contiguity), and guides teams on flattening, memory layout, and JIT/bounds-check behavior in hot numeric code.

## The core idea An **array** is a fixed-size, indexed container of values of one type. A **multidimensional array** conceptually is a grid (2D), cube (3D), etc. Many languages (C, Fortran) implement a 2D array as one **contiguous** block of memory of size rows×columns, with the compiler computing the address `base + (i*columns + j)*elementSize`. That is a *true rectangular array*: every row is guaranteed the same length and laid out back-to-back. **Java does not have this.** In Java, the type `int[][]` literally means "an array whose element type is `int[]`" — an **array of arrays**, also called a *nested* or *jagged-capable* array. ## What that means concretely Write `int[][] a = new int[3][4];`. Java creates: - one **outer** array of length 3 whose element type is `int[]` (it holds 3 *references*), - three **inner** `int[]` arrays, each of length 4, - and stores a reference to each inner array in the outer array's slots. So `a` is a reference to the outer array. `a[0]`, `a[1]`, `a[2]` are each a reference to a separate `int[]` object. These three inner arrays are independent heap objects and are **not** required to sit next to each other in memory. Indexing `a[i][j]` is two steps: (1) read `a[i]` — follow a reference to get the i-th inner array; (2) index `[j]` into that inner array. That is **two pointer dereferences**, versus one address computation in a contiguous layout. ## Why "no true rectangular array" Because each row is an independent object, nothing forces the rows to be equal length. `new int[3][4]` happens to make them all length 4, but you can replace any row: `a[1] = new int[10];` is legal. The "rectangle" is only a convention, not a language guarantee. The lengths are queried per level: `a.length` is the number of rows (the outer length); `a[i].length` is the length of row i. ## Consequences 1. **Jagged arrays are free** — rows of different lengths are the natural case, not a special feature (see the dedicated jagged-array question). 2. **Sub-arrays can be shared/aliased** — `int[] row = a[1];` makes `row` and `a[1]` the same object; mutating one is seen through the other. 3. **Performance** — extra indirection plus rows scattered on the heap means worse **cache locality** (the CPU caches contiguous memory; jumping between far-apart rows causes more cache misses) than a flat contiguous array. 4. **Initialization** — a freshly `new int[3][4]` array has every element at the default (`0` for int, `null` for references, `false` for boolean, etc.). ## Higher dimensions The pattern recurses: `int[][][]` is an array of (arrays of (arrays of int)). `new int[2][3][4]` builds one outer length-2 array, two length-3 arrays, and six length-4 arrays — 9 array objects in total. ## When it matters For most code the convenience wins. For numeric-heavy, performance-critical work, practitioners often **flatten** to a single `int[]` of size rows*cols and index manually as `data[i*cols + j]` to recover the contiguous, cache-friendly C-style layout.

  • How many array objects does new int[2][3][4] create in total?
    Nine: one outer length-2 array, two length-3 arrays it points to, and six length-4 int arrays (2×3) at the bottom — 1 + 2 + 6 = 9.
  • How would you get a true contiguous layout for performance?
    Flatten to a single 1D array of size rows*cols and index as data[i*cols + j], which restores C-style contiguity and cache locality.

saying these in an interview costs you the question

  • Saying Java stores 2D arrays as one contiguous rows×columns block
  • Assuming a.length gives the column count or total element count
  • Believing all rows must be the same length
  • Thinking a[i] is a value rather than a reference to a separate array

context