Struct Vs Class When To Use Which

Rapid overview

🧠 Conceptual Summary

Featurestructclass
Type categoryValue typeReference type
Memory allocationStored inline (stack, or inside another object)Stored on heap, referenced via pointer
Default behaviorCopied by value (creates a full copy)Copied by reference (points to same object)
NullabilityCannot be null (unless Nullable<T>)Can be null
InheritanceCannot inherit from another struct or class; only from ValueTypeSupports inheritance and polymorphism
InterfacesCan implement interfacesCan implement interfaces and base classes
Default constructorCannot define a custom parameterless constructor (C# 10 adds limited support)Can freely define constructors
Finalizer / DestructorNot supportedSupported
GC behaviorUsually short-lived, reclaimed when out of scopeManaged by the Garbage Collector
Boxing / UnboxingConverting to/from object/interface causes allocationNo boxing/unboxing issues
Thread safetySafer for small immutable dataReference types require synchronization if shared

---

βš™οΈ Practical Explanation (How CLR Handles Them)

🧩 struct

  • Lives inline β€” if it’s a local variable, it’s on the stack; if it’s a field in another object, it’s inside that object’s memory layout.
  • When passed to a method, a full copy is made (unless passed by ref or in).
  • Ideal for small, immutable, lightweight data β€” e.g., coordinates, ticks, prices, GUIDs.

Example:

struct Point
{
    public int X;
    public int Y;
}

Each Point lives inline β€” no GC pressure.

---

🧩 class

  • Lives on the managed heap. Variables hold a reference (pointer) to the actual object.
  • Passed around by reference, so multiple variables can point to the same instance.
  • Managed by the Garbage Collector.

Example:

class Order
{
    public string Symbol { get; set; }
    public double Price { get; set; }
}

Each Order allocation hits the heap and is tracked by the GC.

---

⚑ Performance and Design Implications

βœ… When to use struct

Use when:

  • The object is small (≀ 16 bytes typically).
  • It’s immutable.
  • You’ll create many of them (e.g., millions per second) and want no GC overhead.
  • Value semantics make sense (copying creates independence).

Example (trading context):

readonly struct Tick
{
    public string Symbol { get; }
    public double Bid { get; }
    public double Ask { get; }
}

Each Tick represents an immutable market data point. Perfect as a struct.

---

🚫 When NOT to use struct

Avoid when:

  • It’s large (lots of fields) β†’ copying becomes expensive.
  • You need polymorphism, inheritance, or shared references.
  • You mutate the same instance in multiple places.

---

⚠️ Boxing and Hidden Allocations

When a struct is treated as an object or cast to an interface, it gets boxed β€” copied onto the heap.

struct Point { public int X, Y; }

object obj = new Point(); // BOXED: allocates on heap
Point p = (Point)obj;     // UNBOXED: copy back to stack

So: value types are not automatically zero-GC β€” you must use them carefully.

---

🧩 Real-World Example (HFM context)

If you’re processing millions of tick messages per second:

  • Use a struct (or readonly struct) for individual ticks (lightweight, immutable).
  • Use a class for services and entities that manage state, like OrderBook, TradeSession, or CacheManager.

Example:

readonly struct Tick
{
    public string Symbol { get; init; }
    public double Bid { get; init; }
    public double Ask { get; init; }
}

class PriceFeedProcessor
{
    private readonly List<Tick> _ticks = new();

    public void OnTick(Tick tick) => _ticks.Add(tick);
}

---

🧩 Memory Visualization

Stack:
 β”œβ”€ Tick t1 { X=1, Y=2 }   (struct: inline)
 β”œβ”€ Tick t2 = t1 (copied!)
 └─ Order ref ─┐
               β–Ό
Heap:
 └─ { Symbol="EURUSD", Price=1.0734 }  (class: heap object)

---

🎯 Senior-level 20-second summary (how to answer in interview)

β€œA struct is a value type, stored inline and copied by value β€” great for small, immutable data and reducing GC pressure. A class is a reference type, stored on the heap, supporting inheritance and polymorphism. Structs avoid GC but can cost more to copy if large, and boxing them defeats their advantage. In low-latency systems, we often use small structs like Tick or Point to keep memory tight and predictable.”

---

Would you like me to give you 5 advanced interview questions around struct vs class (with answers), the kind might use to test your understanding of memory, GC, and performance?

Excellent β€” this visual explanation will make your answer stand out instantly in a senior-level C# interview (especially at, where they care about performance, GC, and data flow).

Let’s walk through it step by step, from memory layout to how structs and classes behave under assignment and method calls.

---

🧩 Memory Behavior: struct vs class

1️⃣ Basic memory layout

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Stack               β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚ β”‚ int x = 10;             β”‚  β”‚
β”‚ β”‚ Point p = {X=1,Y=2};    β”‚  β”‚  ← Struct (value type)
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚   (lives inline here)        β”‚
β”‚                              β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚ β”‚ Order o ────────────────┼──┼──► Heap
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Heap               β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚ β”‚ Order { Id=1, Price=99 }β”‚  β”‚  ← Class (reference type)
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Explanation:

  • Struct (Point) is stored directly on the stack or inline within another object.
  • Class (Order) is stored on the heap; variables on the stack hold a reference (pointer) to it.

---

Questions & Answers

Q: When should you choose a struct over a class?

A: When the data is small (≀16 bytes), immutable, frequently created, and benefits from value semantics. Structs reduce GC pressure by living inline and being collected with stack frames. Its data is stored on the stack (for locals), or inline inside another object (as part of that object’s memory). This means

  • No separate heap allocation
  • No object header
  • No pointer indirection

Stack memory is automatically reclaimed when a method returns. No garbage collection is needed for stack-allocated data. This is much cheaper than heap allocation + GC.

public readonly struct Money
{
    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public decimal Amount { get; }
    public string Currency { get; }
}

void Calculate()
{
    Money price = new Money(100, "USD");
    // price is on the stack
} // stack frame is popped β†’ memory reclaimed immediately

⚠️ Structs are not always stack-allocated:

  • If they are fields of a heap object, they live inside that object
  • If they are boxed (cast to object or interface), they go to the heap
  • Large structs copied often can hurt performance

Q: What pitfalls occur when structs are too large?

A: Copies become expensive, especially when passing by value. This can negate performance gains and increase stack usage. Use in/ref parameters or switch to classes if the struct grows.

Q: How does boxing affect struct performance?

A: Boxing copies the struct onto the heap and allocates, defeating the GC benefits. Avoid passing structs to APIs expecting object or non-generic interfaces to prevent boxing.

Q: Can structs have parameterless constructors?

A: Starting with C# 10, yes, but they must be public/private and initialize all fields. Historically, structs always had an implicit default constructor. Remember that every struct has a zeroed default state.

Q: How do you prevent copying when passing structs to methods?

A: Use in (readonly ref) for read-only access, or ref/ref readonly when you need to mutate or avoid copies. This keeps performance predictable for larger structs.

Q: Can structs inherit from classes?

A: No. Structs are sealed value types that inherit from ValueType. They can implement interfaces but cannot participate in class inheritance hierarchies.

Q: When do structs hurt cache locality?

A: Rarelyβ€”they often improve locality. However, large structs embedded in arrays can cause cache misses due to size. Evaluate data layout to ensure structs remain lean.

Q: How do you model optional structs?

A: Use Nullable<T> (Tick?). It wraps the struct with a HasValue flag, allowing null-like semantics without resorting to classes.

Q: What about mutability?

A: Prefer immutable structs to avoid accidental copies followed by mutation. Mutable structs can lead to confusing bugs when copies diverge silently.

Q: How do structs interact with pattern matching and deconstruction?

A: They support Deconstruct methods and pattern matching just like classes. This makes them ergonomic for lightweight domain data while still keeping value semantics.

---

2️⃣ Assignment behavior

βœ… Struct (value type)

Point a = new Point { X = 1, Y = 2 };
Point b = a;    // copy!
b.X = 99;
Console.WriteLine(a.X); // 1 (a unaffected)

Memory:

Stack:
 a { X=1, Y=2 }
 b { X=99, Y=2 }   ← completely separate copy
  • Structs are copied by value.
  • Each variable has its own independent copy.
  • No heap allocation β†’ no GC pressure.

---

βœ… Class (reference type)

Order o1 = new Order { Id = 1, Price = 99 };
Order o2 = o1;  // copy reference!
o2.Price = 120;
Console.WriteLine(o1.Price); // 120

Memory:

Stack:
 o1 ─┐
 o2 β”€β”˜β”€β”€β–Ί Heap: { Id=1, Price=120 }
  • Classes are copied by reference β€” both variables point to the same heap object.
  • Modifying one affects the other.

---

3️⃣ Struct inside a class (inline layout)

class Trade
{
    public string Symbol;
    public Point Position;
}

Memory:

Heap: Trade
 β”œβ”€ Symbol β†’ "EURUSD"   (heap reference)
 └─ Position { X=10, Y=20 }  (inline in Trade object)

Insight: Even though the struct is inside a class (on heap), its fields are embedded inline β€” not separate allocations. This reduces pointer indirection and helps cache locality.

---

4️⃣ Passing to methods

void Move(Point p) { p.X += 10; } // copy!
void MoveRef(ref Point p) { p.X += 10; } // modifies original

Memory visualization:

By value (copy):
 Caller: a { X=1 }
 Method: p { X=1 } β†’ modified to X=11 (copy destroyed)

By ref:
 Caller: a { X=1 }
 Method: p ─┐
             └─ modifies same memory β†’ X=11 persists

πŸ’‘ Interview tip:

β€œStructs are copied on method calls unless passed by ref or in. Large structs should be passed by in to avoid copy overhead β€” especially in tight loops or latency-critical code.”

---

5️⃣ Heap fragmentation and GC difference

Structs:
[Stack]
[Stack frame destroyed β†’ data gone instantly]
β†’ No GC involvement.

Classes:
[Heap]
[Objects live until unreachable]
β†’ GC scans and collects them (Gen0β†’Gen1β†’Gen2)

Key insight:

  • Structs vanish when they go out of scope β†’ predictable lifetime.
  • Classes depend on GC cycles β†’ non-deterministic reclamation.
  • Overusing classes in a high-frequency path (like market ticks) causes GC churn and pauses.

---

6️⃣ βš™οΈ Summary Diagram

STRUCT (Value Type)
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ Inline Data  β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚ Copied on =  β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚ No GC        β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚ Pass by ref  β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     ↓
  Great for small immutable data

CLASS (Reference Type)
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ Heap Object  β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚ Copied ref   β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚ Managed by GCβ”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚ Supports OOP β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     ↓
  Great for shared mutable state

---

🧠 Quick β€œwhiteboard pitch” for your interview

β€œStructs are value types β€” stored inline, copied by value, no GC involvement, ideal for small immutable data like ticks or coordinates. Classes are reference types β€” heap-allocated, reference-based, and managed by GC. I use structs where I want predictable lifetimes and zero allocations; classes when I need shared, long-lived state or polymorphism.”

---

Would you like me to now create a visual of memory layout with stack/heap arrows (an actual diagram you could memorize or even sketch during the interview)? It would show struct, class, and mixed cases (struct-in-class, class-in-struct) clearly.