Array Slicing

Represent a contiguous subrange of an array as either a view or a copied array.

Array Slicing

Array slicing selects a contiguous subrange from an array. A slice may be a view into the original storage, or it may be a new copied array.

You use it when an algorithm should operate on part of an array without manually passing start and end indices everywhere.

Problem

Given an array $A$ of length $n$, create a slice from index $l$ up to but not including index $r$:

$$ A[l:r] $$

where

$$ 0 \le l \le r \le n $$

Structure

A slice view usually stores:

  • reference to the same backing array
  • start offset
  • length

For example:

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

and

$$ A[1:4] = [3, 5, 1] $$

The slice view points into the same storage rather than copying elements.

Algorithm

Create a slice view:

slice(A, l, r):
    if l < 0 or r > length(A) or l > r:
        error "invalid slice range"

    S.base = A
    S.start = l
    S.length = r - l
    return S

Read from a slice:

get(S, i):
    if i < 0 or i >= S.length:
        error "index out of bounds"

    return S.base[S.start + i]

Example

Let

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

Create:

$$ S = A[1:4] $$

slice index base index value
0 1 3
1 2 5
2 3 1

Reading $S[2]$ maps to:

$$ A[1 + 2] = A[3] = 1 $$

Return $1$.

Correctness

The slice range check ensures that $l$ and $r$ describe a valid contiguous subrange of the base array. The slice stores length $r - l$, so every valid slice index $i$ satisfies $0 \le i < r - l$. Mapping it to $l + i$ gives an index in the original range $[l, r)$, so each slice access returns exactly the corresponding base-array element.

Complexity

operation view slice copied slice
create slice $O(1)$ $O(k)$
get $O(1)$ $O(1)$
set $O(1)$ $O(1)$

Here:

$$ k = r - l $$

A view slice uses:

$$ O(1) $$

extra space.

A copied slice uses:

$$ O(k) $$

extra space.

When to Use

Array slicing is appropriate when:

  • an algorithm works on subranges
  • copying would be unnecessary
  • start and end indices make code noisy
  • recursive or divide-and-conquer logic needs clean boundaries

Be careful with view slices when:

  • mutating the slice may affect the original array
  • a small slice keeps a large backing array alive
  • concurrent code may observe shared storage

Implementation

class Slice:
    def __init__(self, base, start, end):
        if start < 0 or end > len(base) or start > end:
            raise IndexError("invalid slice range")
        self.base = base
        self.start = start
        self.length = end - start

    def get(self, i):
        if i < 0 or i >= self.length:
            raise IndexError("index out of bounds")
        return self.base[self.start + i]

    def set(self, i, x):
        if i < 0 or i >= self.length:
            raise IndexError("index out of bounds")
        self.base[self.start + i] = x
type Slice[T any] struct {
    base  []T
    start int
    size  int
}

func NewSlice[T any](base []T, start, end int) (*Slice[T], bool) {
    if start < 0 || end > len(base) || start > end {
        return nil, false
    }

    return &Slice[T]{
        base:  base,
        start: start,
        size:  end - start,
    }, true
}

func (s *Slice[T]) Get(i int) (T, bool) {
    var zero T
    if i < 0 || i >= s.size {
        return zero, false
    }
    return s.base[s.start+i], true
}

func (s *Slice[T]) Set(i int, x T) bool {
    if i < 0 || i >= s.size {
        return false
    }
    s.base[s.start+i] = x
    return true
}