Jagged Array

Store rows of different lengths as an array of arrays.

Jagged Array

A jagged array stores a collection of rows where each row can have a different length. It is usually represented as an array of arrays.

You use it when the data is grouped by row, but each group may contain a different number of elements.

Problem

Given a jagged array $A$, support reading and writing an element at row $i$ and column $j$ where:

$$ 0 \le i < rows(A) $$

and

$$ 0 \le j < length(A[i]) $$

Structure

A jagged array stores each row separately:

$$ A = [ [8, 3, 5], [1], [9, 4] ] $$

Rows do not need the same length.

Algorithm

Read an element:

get(A, i, j):
    if i < 0 or i >= rows(A):
        error "row index out of bounds"
    if j < 0 or j >= length(A[i]):
        error "column index out of bounds"
    return A[i][j]

Write an element:

set(A, i, j, x):
    if i < 0 or i >= rows(A):
        error "row index out of bounds"
    if j < 0 or j >= length(A[i]):
        error "column index out of bounds"
    A[i][j] = x

Example

Let

$$ A = [ [8, 3, 5], [1], [9, 4] ] $$

To read position $(2, 1)$:

step action result
1 check row $0 \le 2 < 3$
2 check column $0 \le 1 < 2$
3 read value $4$

Return $4$.

Correctness

The row bounds check ensures that $A[i]$ exists. The column bounds check ensures that index $j$ exists inside that specific row. Since each row owns its own storage, reading or writing $A[i][j]$ affects exactly the requested element.

Complexity

operation time
get $O(1)$
set $O(1)$
append row $O(1)$ amortized
scan all elements $O(m)$

Here $m$ is the total number of stored elements across all rows.

Space usage:

$$ O(m + r) $$

where $r$ is the number of rows.

When to Use

Jagged arrays are appropriate when:

  • rows have different lengths
  • rectangular storage would waste space
  • each row is processed independently
  • adjacency lists or grouped records are needed

They are less suitable when uniform matrix operations or cache-friendly full-row scans dominate.

Implementation

class JaggedArray:
    def __init__(self):
        self.rows = []

    def append_row(self, row):
        self.rows.append(list(row))

    def get(self, i, j):
        if i < 0 or i >= len(self.rows):
            raise IndexError("row index out of bounds")
        if j < 0 or j >= len(self.rows[i]):
            raise IndexError("column index out of bounds")
        return self.rows[i][j]

    def set(self, i, j, x):
        if i < 0 or i >= len(self.rows):
            raise IndexError("row index out of bounds")
        if j < 0 or j >= len(self.rows[i]):
            raise IndexError("column index out of bounds")
        self.rows[i][j] = x
type JaggedArray[T any] struct {
    rows [][]T
}

func (j *JaggedArray[T]) AppendRow(row []T) {
    copied := make([]T, len(row))
    copy(copied, row)
    j.rows = append(j.rows, copied)
}

func (j *JaggedArray[T]) Get(i, k int) (T, bool) {
    var zero T
    if i < 0 || i >= len(j.rows) {
        return zero, false
    }
    if k < 0 || k >= len(j.rows[i]) {
        return zero, false
    }
    return j.rows[i][k], true
}

func (j *JaggedArray[T]) Set(i, k int, x T) bool {
    if i < 0 || i >= len(j.rows) {
        return false
    }
    if k < 0 || k >= len(j.rows[i]) {
        return false
    }
    j.rows[i][k] = x
    return true
}