GDKB

Arrays

Beginner20 Minutes readUpdated July 21, 20263 related concepts

An array is a fixed-size, ordered block of elements of the same type, stored contiguously in memory and accessed by numeric index. It is the simplest structure for holding "many of the same thing" — an inventory's item slots, a level's tile grid, a particle system's positions — and it underlies almost every higher-level collection a language provides.

On this page

Arrays

Summary

An array is a fixed-size, ordered block of elements of the same type, stored contiguously in memory and accessed by numeric index. It is the simplest structure for holding "many of the same thing" — an inventory's item slots, a level's tile grid, a particle system's positions — and it underlies almost every higher-level collection a language provides.

Mental Model

Think of an array as a row of identical lockers bolted together in a single row, numbered starting at 0. Because every locker is the same size and they sit side by side, the computer can jump straight to locker 7 by doing simple math (start address + 7 × locker size) instead of walking past lockers 0 through 6 first. That direct-jump property — O(1) random access — is the entire reason arrays exist as a distinct concept rather than just "a list of things."

The row is bolted together once, at a fixed length. You cannot insert a new locker in the middle without rebuilding the whole row — every locker after the insertion point has to shift down to make room. This is the trade-off that motivates dynamic collections (see [[CON-0013]]) once an array's fixed size becomes a problem.

Core Explanation

An array's defining properties are:

  • Fixed length. Most languages decide an array's length at creation and never change it. int[] scores = new int[10]; allocates exactly 10 slots — asking for a 11th (scores[10]) is an out-of-bounds access, not automatic growth.
  • Contiguous memory. All elements sit next to each other, which is what makes indexing arithmetic instead of a search, and is also what makes arrays cache-friendly: reading scores[3] after scores[2] is very likely to already be in the CPU cache.
  • Homogeneous element type. Every slot holds the same type and therefore the same size, which is what makes the index-to-address math possible in the first place.
  • Zero-based indexing. The first element is array[0], not array[1], in virtually every language a game studio uses (C#, C++, and Blueprint's array nodes all agree on this). The last valid index is Length - 1.

Multi-dimensional arrays (int[,] grid = new int[width, height]; in C#, or a jagged int[][]) extend the same idea to more than one axis — a tile map is commonly a 2D array where grid[x, y] addresses one tile.

Arrays are value-holding containers, but the array variable itself behaves like a reference type in C#/Blueprint: assigning one array variable to another copies the reference, not the elements, so both variables see the same underlying data (see Common Mistakes).

Syntax / Examples

C# (Unity)

csharp
public class Inventory : MonoBehaviour
{
    // Fixed-size array: exactly 5 item slots, ever.
    public string[] slots = new string[5];

    void Start()
    {
        slots[0] = "Health Potion";
        slots[1] = "Rusty Sword";

        for (int i = 0; i < slots.Length; i++)
        {
            Debug.Log($"Slot {i}: {slots[i] ?? "empty"}");
        }
    }
}

C++ (Unreal)

cpp
// A fixed-size C-style array of exactly 5 slots.
FString Slots[5];

void AInventory::BeginPlay()
{
    Super::BeginPlay();

    Slots[0] = TEXT("Health Potion");
    Slots[1] = TEXT("Rusty Sword");

    for (int32 Index = 0; Index < 5; ++Index)
    {
        UE_LOG(LogTemp, Log, TEXT("Slot %d: %s"), Index, *Slots[Index]);
    }
}

Unreal Blueprint

A variable typed as e.g. Integer with the Array checkbox enabled becomes a fixed-length-at-runtime array in the graph. Get (a copy) and Set nodes take an index pin; Array Length replaces .Length; a For Each Loop node replaces the C#/C++ for loop shown above.

Practical Example

A tile-based level needs to know, for any grid cell, whether it blocks movement.

text
public class CollisionGrid : MonoBehaviour
{
    public int Width = 10;
    public int Height = 10;
    private bool[] blocked;

    void Awake()
    {
        blocked = new bool[Width * Height];
    }

    int Index(int x, int y) => y * Width + x;

    public bool IsBlocked(int x, int y) => blocked[Index(x, y)];

    public void SetBlocked(int x, int y, bool value)
    {
        blocked[Index(x, y)] = value;
    }
}

A single 1D array backs a logically 2D grid via y * Width + x — the same contiguous-memory idea as a true multi-dimensional array, made explicit so it's clear why the math works: row y starts y * Width slots into the array, and x offsets within that row.

Common Mistakes

  • Off-by-one / out-of-bounds access. for (int i = 0; i <= slots.Length; i++) reads one slot past the end and throws (C#) or reads garbage/corrupts memory (C++). The loop condition should be i < slots.Length, not <=.
  • Assuming assignment copies the array. int[] a = original; a[0] = 99; also changes original[0] in C#, because a and original reference the same underlying array. Use Array.Clone() or a manual copy loop if an independent copy is actually needed.
  • Reaching for a fixed array when the size isn't known up front. If elements will be added or removed over the object's lifetime, a fixed array is the wrong tool — see [[CON-0013]] instead of manually reallocating and copying on every resize.
  • Forgetting arrays default-initialize. A freshly allocated new int[5] is all zeros in C#, not uninitialized garbage — but a raw C++ array (int values[5];) on the stack is not zero-initialized unless explicitly value-initialized (int values[5]{};).

Best Practices

  • Use .Length (C#) or a named constant/sizeof-derived count (C++) instead of hard-coding a magic number for the array's size anywhere the size could plausibly change.
  • Bounds-check any index computed from user input, save data, or another system before indexing — a corrupted save file should produce a handled error, not a crash.
  • Prefer a 1D array with manual index math (as in the Practical Example) over a jagged array for large 2D grids — it is one contiguous allocation instead of Height separate ones, which is both faster to iterate and friendlier to the cache.
  • Reach for a dynamic collection ([[CON-0013]]) the moment "how many elements" stops being known at allocation time — resizing an array by hand is exactly what those collections already do correctly.

Exercises

  1. Write a method that takes an int[] and returns the index of its largest element, without using any built-in "max" helper. Handle the empty-array case explicitly.
  2. Given int[] a = { 1, 2, 3 }; int[] b = a; b[0] = 99;, predict the value of a[0] before running it, then verify. Explain the result in terms of value vs. reference semantics from [[CON-0001]].
  3. Implement the Index(x, y) grid mapping from the Practical Example for a grid that is not square, and write a test that walks every (x, y) pair and confirms no two pairs map to the same index.
  4. Convert a fixed 2D bool[,] collision grid into the flattened 1D version shown above, and measure (informally) whether iterating all cells row-by-row feels different in practice.
  • Collections — dynamic, resizable containers built on top of the same contiguous-memory idea, for when an array's fixed length becomes a limitation.
  • Structs — arrays of structs pack value-type data contiguously, which is often the fastest way to store many small records.
  • Strings — a string is, under the hood, an array of characters; many string bugs are array bugs in disguise.

References

Related Concepts