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]afterscores[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], notarray[1], in virtually every language a game studio uses (C#, C++, and Blueprint's array nodes all agree on this). The last valid index isLength - 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)
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)
// 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.
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 bei < slots.Length, not<=. - Assuming assignment copies the array.
int[] a = original; a[0] = 99;also changesoriginal[0]in C#, becauseaandoriginalreference the same underlying array. UseArray.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
Heightseparate 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
- 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. - Given
int[] a = { 1, 2, 3 }; int[] b = a; b[0] = 99;, predict the value ofa[0]before running it, then verify. Explain the result in terms of value vs. reference semantics from [[CON-0001]]. - 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. - 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.
Related Concepts
- 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
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules, which this document follows.- Unity Scripting Reference — Arrays: https://docs.unity3d.com/Manual/class-Array.html
- Unreal Engine C++ documentation — Arrays: https://dev.epicgames.com/documentation/en-us/unreal-engine/array-containers-in-unreal-engine