data-oriented design (SoA versus AoS)
/ SoA: ess-oh-AY; AoS: ay-oh-ESS /
Suppose you have a million game characters, each with a position, a velocity, a name, and a hundred other fields, and every frame you update just the positions and velocities. The natural object-oriented layout stores each character as one struct and keeps an array of them — but when you stream through updating only two of the hundred-and-two fields, the hardware drags the OTHER hundred fields along for the ride, because memory comes in cache lines. Data-oriented design starts from the opposite premise: design your data layout around how it is actually accessed in bulk, not around tidy objects.
The central choice has two names. Array of structs (AoS) is the familiar layout: one array, each element a full struct, so a character's fields sit together and the array interleaves field1,field2,...,field1,field2,... in memory. Struct of arrays (SoA) instead keeps one separate array per field: all the positions in one contiguous array, all the velocities in another, and so on. Now the loop that updates positions and velocities walks two tight, contiguous arrays, every loaded cache line is 100% useful data, and the hardware prefetcher streams perfectly — whereas with AoS each loaded line was mostly the unused name and other fields. SoA also lets the compiler vectorize (apply one SIMD instruction across many positions at once), because the values it needs are packed adjacently. The broader data-oriented mindset is: identify the hot transformation, see exactly which bytes it touches, and lay memory out so those bytes are dense and sequential — improving spatial locality so the caches and prefetcher work for you.
It matters because for data-heavy, bulk-processing code (simulations, ECS game engines, columnar databases, numerical kernels) the SoA-versus-AoS choice alone routinely changes throughput by several times, with no algorithm change. The honest caveats: SoA is not universally better. If you usually touch ALL of an object's fields together (random access to one whole record at a time), AoS keeps that record on one cache line and wins, while SoA would scatter it across many arrays. SoA also makes 'one object' awkward to pass around and can complicate insertion and deletion. So the rule is not 'always SoA' — it is choose the layout that matches your dominant access pattern, and measure, because the cross-over depends on which fields are hot and how you traverse them.
AoS: struct P { float x,y,z; char name[64]; } a[N]; // update x,y,z -> each line mostly name (wasted) SoA: struct { float x[N], y[N], z[N]; char name[N][64]; } a; // update x,y,z -> 100% useful lines, vectorizable
Updating only x,y,z: AoS drags the unused name through cache every element; SoA packs the hot fields so every byte loaded is used.
SoA wins when you stream a few fields across many objects; AoS wins when you touch all of one object's fields together. Neither is universally faster — pick the layout matching your hot access pattern, and confirm with a measurement.