Structs vs Classes in Unity: The Memory Decision That Killed My Frame Rate (And How I Fixed It)
Here's the thing: I once spent an entire afternoon watching my Unity project's frame rate tank from a smooth 60 FPS to a stuttering mess, and I couldn't figure out why. The Profiler was screaming about garbage collection spikes, and I was creating thousands of bullet objects every second in my top-down shooter. The culprit? I was using classes for my bullet data when I should've been using structs. That one decision about structs vs classes in Unity was the difference between a playable game and a slideshow.
Been there? You're writing C# code in Unity, you need to store some data, and you just... pick class because that's what feels familiar. Or maybe you've heard structs are "faster" but you're not really sure when to use them or why it matters. Trust me, understanding this distinction isn't just academic mumbo-jumbo—it's the kind of knowledge that separates games that run smoothly from ones that stutter and frustrate players.
What Makes Structs and Classes Different in Unity
Let me break this down the way I wish someone had explained it to me when I was getting started with Unity game development.
The fundamental difference comes down to value types versus reference types. When you create a struct, you're creating a value type—the actual data is stored directly where you declare it in memory. Pass that struct to a method? You're making a complete copy of all its data. Assign it to another variable? Another full copy.
Classes work completely differently. They're reference types, which means the variable doesn't hold the actual data—it holds a memory address pointing to where the data lives. When you pass a class instance to a method or assign it to another variable, you're only copying that reference, not the entire object. Both variables end up pointing to the exact same object in memory.
This distinction determines where your data lives in memory:
- The Stack: This is where value types like structs typically hang out. It's a region of memory used for static memory allocation, and it's lightning fast to access. The catch? It's limited in size.
- The Heap: This is the larger, more flexible memory region where reference types like classes live. It's used for dynamic memory allocation, so it can grow as needed. But accessing the heap is slower than the stack, and here's the kicker—it's managed by the Garbage Collector.
- Garbage Collection (GC): This is Unity's automatic memory management process that cleans up objects on the heap that aren't being used anymore. Sounds helpful, right? The problem is that when the Garbage Collector runs, it can cause those dreaded performance spikes and stutters that make your game feel janky.
One more thing: in Unity, all MonoBehaviour scripts must be classes. You don't get a choice there. But for your data containers—the objects that hold stats, item information, coordinates—that's where this decision becomes crucial.
The Memory Story: Where Your Data Actually Lives
Let me show you exactly what I mean with some code. This is the kind of stuff that took me months to really internalize, but once you see it in action, it clicks.
First, here's a simple struct for holding player stats:
// A simple struct to hold player stats.
// It's marked [System.Serializable] to be visible in the Unity Inspector.
[System.Serializable]
public struct PlayerStats
{
public int health;
public float speed;
}
And here's a similar data container, but as a class:
// A class to manage player behavior.
[System.Serializable]
public class PlayerData
{
public string playerName;
public int score;
public void AddScore(int amount)
{
score += amount;
}
}
Now watch what happens when we pass these to methods. This is where the C# structs and classes distinction becomes really important.
Passing by Value (Structs):
void CauseDamage(PlayerStats stats)
{
// This only changes the local copy of the stats.
stats.health -= 10;
}
PlayerStats myStats = new PlayerStats { health = 100 };
CauseDamage(myStats);
// myStats.health is still 100 here.
The struct gets copied when you pass it to CauseDamage. The method works on that copy, and your original myStats remains completely untouched. This behavior is predictable and safe—you can't accidentally modify data you didn't intend to.
Passing by Reference (Classes):
void AddToScore(PlayerData data)
{
// This modifies the original object that the reference points to.
data.score += 50;
}
PlayerData myData = new PlayerData { score = 0 };
AddToScore(myData);
// myData.score is now 50 here.
With the class, only the reference gets copied. The method receives a reference to the original object, so when it modifies data.score, it's actually changing myData.score because they both point to the same object in memory.
Why Structs vs Classes in Unity Matters for Your Game's Performance
Here's where theory meets reality. The choice between structs and classes directly impacts your game's frame rate and how smooth the player experience feels.
When you choose structs for small, frequently created data, you're avoiding the heap entirely (assuming the struct isn't part of a class). This means:
- No garbage collection pressure. The stack automatically cleans up when you leave the scope. No GC spikes, no stutters.
- Faster allocation. Grabbing memory from the stack is essentially just moving a pointer. It's incredibly fast.
- Better cache performance. Data on the stack is typically more tightly packed, which means your CPU can grab it faster.
This is why structs are perfect for things like particle data, bullet information, or grid coordinates—things you're creating and destroying constantly. In my shooter example from earlier, switching my bullet data from a class to a struct eliminated those GC spikes entirely.
Classes shine when you're dealing with:
- Complex objects with identity and behavior
- Large data structures
- Objects that need inheritance
- Anything that's a
MonoBehaviour
The heap is slower to allocate on, but you're only passing around small references (typically 8 bytes on a 64-bit system), not the entire object. Multiple parts of your code can share the same object efficiently.
When I Reach for a Struct Instead of a Class
After years of Unity game development, I've developed a rule of thumb that's served me well: use a struct when your object represents a single value and has a small memory footprint.
Perfect candidates for structs:
- Coordinates:
GridPoint,IntVector2 - Simple data containers:
ItemData,BulletInfo - Things that feel like enhanced primitives:
DamageAmount,HealthPoints
Unity itself follows this pattern. Look at the built-in types you use every day: Vector3, Quaternion, Color—they're all structs. Unity's engineers made these structs because they're small (12-16 bytes typically), you use them constantly, and copying them is cheaper than managing heap references.
Here's a perfect example from my own code—a simple grid coordinate:
// Perfect use case for a struct: a simple coordinate pair.
public struct GridPoint
{
public int x;
public int y;
}
Two integers, 8 bytes total. Lightning fast to copy, no GC overhead, perfect for representing positions in a tile-based game.
Here's My Cheat Sheet for Choosing Between Them
After years of making these decisions, I've put together a quick-reference table that I keep coming back to. This is the cheat sheet I wish I had when I was starting out:
| Criteria | Approach A: Structs | Approach B: Classes |
|---|---|---|
| Best For | Small, immutable data containers that represent a single value (e.g., Vector3, Quaternion, custom data like ItemData). |
Complex objects with identity and behavior, especially those that need to be inherited from, like MonoBehaviour scripts. |
| Performance | Generally faster for small data types due to stack allocation, avoiding garbage collection pressure. Can be slower if the struct is very large and copied frequently. | Slower initial allocation on the heap. Can cause performance spikes (GC alloc) when many objects are created and destroyed. |
| Memory | Stored on the stack (unless part of a class), which is efficient. Each copy takes up new memory. | Stored on the heap, with only a small reference stored on the stack. Multiple variables can point to the same memory location. |
| Code Example | public struct GameItem { public int itemID; public string name; } |
public class Enemy { public int health; public void TakeDamage(int amount) { health -= amount; } } |
The Readonly Struct Trick That Saves You From Bugs
One of my favorite techniques in modern C# is marking structs as readonly. This is something I always recommend to developers I'm mentoring, and here's why it's brilliant.
When you make a struct readonly, you're telling the compiler: "This struct's values can only be set during construction, and never again." This has two huge benefits:
- Code clarity: Anyone reading your code immediately knows this data is immutable—it won't change after creation.
- Compiler optimizations: The compiler can make additional performance improvements when it knows data won't mutate.
Here's how I use it for item data in a game:
// This struct's values can only be set in the constructor.
public readonly struct ItemData
{
public readonly int ItemID;
public readonly string ItemName;
public ItemData(int id, string name)
{
ItemID = id;
ItemName = name;
}
}
Once you create an ItemData, it never changes. That ItemID will always be the same. This pattern prevents entire categories of bugs where you accidentally modify data you thought was constant.
What Not to Do: The Large Struct Mistake I Made
Let me tell you about a mistake I made early on that cost me a good afternoon of debugging.
I had learned that structs were "faster," so I got overeager. I created a struct to hold enemy data—position, rotation, health, multiple state variables, a string for the enemy type, references to prefabs... it was huge. Probably 80+ bytes.
Then I passed this struct to about a dozen different methods throughout my AI system. Every. Single. Time. I. Called. A. Method., Unity was copying 80+ bytes. The performance was actually worse than if I'd just used a class.
Here's the rule: Avoid large structs. If your data structure is big (generally over 16-24 bytes), or if you're passing it around frequently, use a class instead. The overhead of copying large structs will hurt you more than the GC pressure from small class allocations.
Boxing—The Hidden Performance Killer You Need to Avoid
This one trips up a lot of beginners, and it's sneaky because the code looks innocent.
Boxing happens when you treat a value type (like a struct or an int) as a reference type (like object). When this happens, C# has to wrap your value type in a class and put it on the heap. This creates garbage and completely negates all the performance benefits of using a value type in the first place.
Here's what boxing looks like:
// This causes boxing, which creates garbage.
object myValue = 10; // An int (value type) is put inside an object (reference type).
That innocent-looking line just created an object on the heap. Do this in a loop that runs every frame, and you're back to GC spikes and stuttering.
Common places where boxing happens:
- Using value types with
objectparameters - Adding structs to non-generic collections like
ArrayList - String formatting with composite format strings (though modern C# has optimized this)
The fix? Use generics. Instead of ArrayList, use List<T>. Instead of methods taking object, use generic methods. Modern C# makes it pretty easy to avoid boxing if you're aware of the issue.
How Minecraft Handles Millions of Blocks Without Crashing
Let me share one of my favorite real-world examples of why this stuff matters. When I'm teaching Unity performance optimization, I always bring up Minecraft.
The Mechanic: Minecraft's world is made of millions of blocks—dirt, stone, wood, each with a type and state. The game needs to store and manipulate this enormous amount of data in real-time as players break and place blocks.
The Implementation: Each block is almost certainly represented by a struct. The struct would be tiny—probably just an ID for the block type, maybe a few bits for orientation or state. Storing this massive 3D grid of data as structs instead of classes is absolutely essential for performance.
Why? Imagine if each block was a class—a reference type on the heap. You'd have millions of individual objects, each requiring separate heap allocations, each contributing to GC pressure. The garbage collector would be running constantly trying to manage all these objects. The game would be unplayable.
By using structs, the entire block grid can be stored as a contiguous array in memory. No individual allocations, no GC pressure, excellent cache performance. The data is tightly packed and blazingly fast to access.
The Player Experience: Players get a vast, seamless world that can be modified in real-time. Despite the enormous amount of data being processed, the game runs smoothly. That's the power of choosing the right data structure.
Why RaycastHit is a Struct and Why That's Brilliant
Here's another example I've seen hundreds of implementations of across first-person shooters and action games.
The Mechanic: When a player fires a weapon, Unity performs a raycast to detect what was hit. The raycast returns detailed information about the impact—the point of contact, the surface normal, which collider was hit, and more.
The Implementation: Unity returns this data as a RaycastHit struct. This is a textbook perfect use case for a struct. Here's why:
// RaycastHit is a struct provided by Unity.
RaycastHit hitInfo;
if (Physics.Raycast(transform.position, transform.forward, out hitInfo, 100f))
{
// hitInfo is a copy of the result data, not a reference.
Debug.Log("Hit: " + hitInfo.collider.name);
}
Think about what happens when you fire a machine gun that shoots 10 rounds per second. That's 10 raycasts per second, each returning hit information. If RaycastHit were a class, you'd be creating 10 objects on the heap every second, just from one weapon. Multiply that by multiple enemies, explosions, area-of-effect checks... you'd have hundreds of allocations per second.
By making it a struct, Unity avoids all that garbage. The RaycastHit data is just copied onto the stack, used briefly, and then automatically cleaned up when the method exits. Zero GC pressure.
The Inventory System Pattern Every RPG Uses
One more real-world scenario that shows how professional games combine both approaches effectively.
The Mechanic: In an RPG, the player's inventory contains items—swords, potions, armor—each with properties like damage, weight, and value.
The Implementation: This is where you see the power of combining structs and classes intelligently. The static, unchanging properties of an item (its base stats, name, description) are often stored in a ScriptableObject, which is a class. But when an item exists in the player's inventory, it might be represented by an InventorySlot struct.
This struct could contain:
- A reference to the
ScriptableObject(defining what the item is) - Mutable values like
stackSizeordurability(defining the item's current state)
The inventory itself is just an array of these simple structs. This is incredibly efficient because:
- The item definitions (ScriptableObjects) exist once in memory, shared by all items of that type
- The inventory slots are small, stack-allocated structs
- Checking the inventory, sorting it, or displaying it in UI is fast
Let Me Show You How to Build an Item Database with Structs
Alright, let's get hands-on. I'm going to walk you through exactly how I build a simple item system using structs. This is the kind of implementation I use in prototypes all the time.
The Goal: Create a struct to hold item data, then use it in a manager script to display a list of items.
Step 1: Define the GameItem Struct
First, we create our data container. I mark it [System.Serializable] so it shows up in the Unity Inspector—super helpful for debugging.
// File: GameItem.cs (can be in its own file or inside another)
[System.Serializable]
public struct GameItem
{
public string itemName;
public int itemID;
public int value;
// Structs can have constructors for easy initialization.
public GameItem(string name, int id, int val)
{
itemName = name;
itemID = id;
value = val;
}
}
Notice the constructor? Structs can have constructors, and they're incredibly useful for creating instances with clean, readable code.
Step 2: Create the ItemManager Class
Now we make a MonoBehaviour (which must be a class) that uses our struct:
// File: ItemManager.cs
using UnityEngine;
using System.Collections.Generic;
public class ItemManager : MonoBehaviour
{
// A list of our structs.
public List<GameItem> allItems = new List<GameItem>();
void Start()
{
// Populate the list with some example items.
allItems.Add(new GameItem("Sword", 1, 50));
allItems.Add(new GameItem("Shield", 2, 35));
allItems.Add(new GameItem("Potion", 3, 10));
// Print out the details of our items.
foreach (GameItem item in allItems)
{
Debug.Log($"Item: {item.itemName}, ID: {item.itemID}, Value: {item.value}");
}
}
}
How to Use This:
- Create an empty GameObject in your scene and name it "ItemManager"
- Attach the
ItemManager.csscript to it - Hit Play and check the Console
You'll see your item list printed out. This pattern scales beautifully—you could load hundreds of items from a JSON file, and the performance would still be excellent because each GameItem is just a lightweight struct.
The Value vs Reference Demo That Finally Made It Click
When I'm teaching this concept, here's the demonstration that always makes the lightbulb go on for students. Let me walk you through a side-by-side comparison that makes the difference crystal clear.
The Goal: Create a runnable example that shows exactly how modifying a struct creates a copy, while modifying a class affects the original object.
Step 1: Define a Simple Struct and a Simple Class
// A simple struct for our test.
public struct DamageStruct
{
public int amount;
}
// A nearly identical class for our test.
public class DamageClass
{
public int amount;
}
These are intentionally nearly identical so you can see that the difference is in how they behave, not in how they're written.
Step 2: Create the Test Script
// File: DataTester.cs
using UnityEngine;
public class DataTester : MonoBehaviour
{
void Start()
{
// --- Struct Test (Pass by Value) ---
DamageStruct structDamage = new DamageStruct { amount = 100 };
Debug.Log($"[Struct] Initial Amount: {structDamage.amount}");
// Pass the struct to the method. A copy is made.
ModifyDamage(structDamage);
Debug.Log($"[Struct] Amount After Method: {structDamage.amount}"); // Will still be 100
Debug.Log("--------------------");
// --- Class Test (Pass by Reference) ---
DamageClass classDamage = new DamageClass { amount = 100 };
Debug.Log($"[Class] Initial Amount: {classDamage.amount}");
// Pass the class instance. Only the reference is copied.
ModifyDamage(classDamage);
Debug.Log($"[Class] Amount After Method: {classDamage.amount}"); // Will be 90
}
// Method to modify the struct (operates on a copy)
void ModifyDamage(DamageStruct damage)
{
damage.amount -= 10;
}
// Method to modify the class (operates on the original object)
void ModifyDamage(DamageClass damage)
{
damage.amount -= 10;
}
}
How to Run This:
- Create an empty GameObject named "DataTester"
- Attach the
DataTester.csscript - Hit Play and watch the Console
You'll see:
- The struct's amount stays at 100—the method worked on a copy
- The class's amount changes to 90—the method modified the original
Building a High-Performance Particle System
Let's tackle one more practical implementation—this one shows the real performance benefits of structs when you're dealing with large quantities of data.
The Goal: Create a system that manages thousands of particles using structs to avoid garbage collection and maximize performance.
Step 1: Define the Particle Struct
Here's how I structure particle data:
// File: Particle.cs (or inside ParticleManager.cs)
using UnityEngine;
public struct Particle
{
public Vector3 position;
public Vector3 velocity;
public float lifetime;
}
Three fields: position, velocity, and lifetime. This struct is probably around 28 bytes (two Vector3s at 12 bytes each, plus a float at 4 bytes). Small enough to be efficient as a struct.
Step 2: Create the ParticleManager Class
Now here's where it gets interesting—we're going to manage 10,000 particles:
// File: ParticleManager.cs
using UnityEngine;
public class ParticleManager : MonoBehaviour
{
public int maxParticles = 10000;
private Particle[] _particles; // An array of structs
void Start()
{
// Pre-allocate the array. This is a single, contiguous block of memory.
// No garbage is created here per-particle.
_particles = new Particle[maxParticles];
for (int i = 0; i < maxParticles; i++)
{
// Initialize each particle
_particles[i] = new Particle
{
position = Random.insideUnitSphere * 5f,
velocity = Random.onUnitSphere * 0.5f,
lifetime = Random.Range(1f, 5f)
};
}
}
void Update()
{
// Update all particles. This is very fast because the data is tightly packed.
for (int i = 0; i < maxParticles; i++)
{
_particles[i].position += _particles[i].velocity * Time.deltaTime;
_particles[i].lifetime -= Time.deltaTime;
// Reset particle if its lifetime is over
if (_particles[i].lifetime <= 0)
{
_particles[i].position = Vector3.zero;
_particles[i].lifetime = Random.Range(1f, 5f);
}
}
}
// Visualize the particles using OnDrawGizmos for simplicity
void OnDrawGizmos()
{
if (_particles == null) return;
Gizmos.color = Color.yellow;
for (int i = 0; i < maxParticles; i++)
{
Gizmos.DrawSphere(_particles[i].position, 0.05f);
}
}
}
How to Use This:
- Create an empty GameObject named "ParticleManager"
- Attach the
ParticleManager.csscript - Hit Play and make sure the Scene view is visible
You'll see 10,000 yellow spheres moving around. Here's what makes this fast:
- Single allocation: The
_particlesarray is allocated once inStart(). That's it—no per-particle heap allocations. - Tight memory packing: All 10,000 particles are stored in one contiguous block of memory. Your CPU cache loves this.
- Zero GC pressure: No objects are being created or destroyed during the simulation. The GC stays silent.
If you made each particle a class, you'd be allocating 10,000 separate objects on the heap. The GC would eventually have to clean them all up, causing a massive performance spike. With structs, you avoid that entirely.
What You're Really Getting When You Master This
Alright, let's talk about why spending the time to understand structs vs classes is worth it beyond just "making your code faster."
Performance That Players Actually Notice: When you use structs appropriately, you're directly reducing garbage collection spikes. That means no random frame drops, no stuttering during intense moments. Your game feels responsive and polished. I've seen student projects go from barely maintaining 30 FPS to hitting solid 60 FPS just by optimizing data structures.
Code That's Safer and More Predictable: Because structs are passed by value, you eliminate an entire class of bugs where different parts of your code accidentally modify shared data. When you pass a struct to a method, you know for certain that method can't mess with your original data. That predictability is invaluable as your projects grow larger.
Better Understanding of How Games Actually Work: Learning about value types vs reference types, stack vs heap, and garbage collection isn't just C# trivia—it's understanding how your hardware actually runs your game. This knowledge directly translates to better architectural decisions and more efficient code across everything you build.
The Foundation for Advanced Techniques: Understanding structs is your first step toward more advanced patterns like Unity memory management optimization and data-oriented design. DOTS, Unity's high-performance framework, relies heavily on structs and value types. Master this foundation, and you're ready for the next level.
Ready to Start Building Your First Game?
You've just learned how professional developers optimize memory and performance in Unity—but understanding structs vs classes is just one piece of the puzzle. If you're serious about turning your game ideas into reality, you need a solid foundation across all the fundamentals.
That's why I created Mr. Blocks at Outscal. This course takes you from absolute basics to building a complete, polished game experience. You'll learn by doing, working through real implementations just like the examples we covered today—but with hands-on guidance every step of the way.
Whether you're a college student getting started or someone transitioning into game development like I did, this course gives you the practical skills you need. No fluff, just the real techniques that professional developers use every day.
Key Takeaways
- Structs are value types stored on the stack; classes are reference types stored on the heap. This fundamental difference determines how data is copied, passed, and managed in memory.
- Use structs for small, simple data containers (under 16 bytes) like coordinates, stats, or anything that represents a single value. Use classes for complex objects with behavior, inheritance, or anything that needs to be shared across different parts of your code.
- Structs reduce garbage collection pressure, which eliminates performance spikes and stuttering. For frequently created/destroyed objects like particles or bullets, structs can dramatically improve frame rates.
- Mark structs as
readonlywhen their data shouldn't change after creation. This prevents bugs, improves code clarity, and enables compiler optimizations. - Avoid large structs (over 16-24 bytes) because copying them repeatedly hurts performance more than class allocations. If your data structure is big, use a class instead.
- Boxing (treating value types as reference types) creates garbage and should be avoided. Use generics like
List<T>instead of non-generic collections to prevent boxing. - Professional games like Minecraft use structs for massive data sets (millions of blocks) to avoid catastrophic garbage collection issues. Unity's own types like
Vector3,RaycastHit, andQuaternionare structs for the same reason. - All
MonoBehaviourscripts must be classes—you don't get a choice there. But for data containers, choosing between struct and class is critical for both performance and code safety.
Common Questions About Structs vs Classes in Unity
What is the main difference between structs and classes in Unity?
Structs are value types that store data directly on the stack and are copied when assigned or passed to methods. Classes are reference types that store references to data on the heap, so multiple variables can point to the same object. This means modifying a struct copy doesn't affect the original, while modifying a class instance through any reference affects the original object everywhere.
When should I use a struct instead of a class in Unity game development?
Use structs for small data containers (under 16 bytes) that represent a single value, like coordinates (GridPoint), simple stats (PlayerStats), or data packages that get created and destroyed frequently. Structs are perfect when you want value semantics—where passing the data creates an independent copy—and when you want to avoid garbage collection pressure for performance-critical code.
Why does Unity use structs for Vector3 and Quaternion?
Unity uses structs for Vector3, Quaternion, Color, and similar types because they're small (12-16 bytes), used constantly throughout game code, and copying them is faster than managing heap references and garbage collection. These types represent single values conceptually, making value-type semantics the right choice both for performance and code clarity.
Can structs have methods and constructors?
Yes! Structs can have methods, constructors, and properties just like classes. The key difference is in how they're stored and copied in memory, not in what functionality they can contain. You can create struct constructors for clean initialization and add methods for behavior, though structs can't inherit from other structs or classes (except interfaces).
What happens if I make my struct too large?
If your struct is large (over 16-24 bytes) and you pass it around frequently, the performance cost of copying all that data can exceed the benefits of stack allocation. Every method call that takes your struct as a parameter creates a complete copy of all its data. In these cases, using a class is actually more performant because only a small reference (8 bytes on 64-bit systems) gets copied instead of the entire object.
What is boxing and why should I avoid it?
Boxing occurs when a value type (like a struct or int) is treated as a reference type (like object). C# wraps the value type in a class and puts it on the heap, creating garbage. This negates all the performance benefits of using value types. Avoid boxing by using generic collections (List<T> instead of ArrayList) and generic methods instead of methods that take object parameters.
Do all Unity scripts need to be classes?
Yes, all MonoBehaviour scripts must be classes because they need to inherit from MonoBehaviour, and structs cannot participate in inheritance. Unity's component system requires reference semantics so multiple systems can interact with the same component instance. You only choose between struct and class for your data containers, not for your actual Unity scripts.
How do structs improve Unity performance optimization?
Structs stored on the stack don't create garbage that needs to be collected, eliminating GC spikes that cause stuttering. They're also allocated and deallocated much faster than heap objects. For data created frequently (like particle info or temporary calculations), using structs can reduce frame time significantly. The memory is also more tightly packed, improving CPU cache performance.
What are readonly structs and when should I use them?
A readonly struct is one where all fields are immutable after construction—they can only be set in the constructor. Use readonly structs for data that represents a value that shouldn't change, like item definitions or configuration data. This prevents bugs from accidental modification, makes code intent clearer, and allows the compiler to make additional optimizations.
Can I use structs for Unity memory management in large-scale games?
Absolutely. Professional games use structs extensively for high-performance systems. Games like Minecraft use structs to represent millions of blocks efficiently. Unity's DOTS (Data-Oriented Technology Stack) relies heavily on structs for massive performance gains. The key is understanding when to use them: small, frequently accessed data benefits enormously from struct-based designs, while complex, shared objects are better as classes.