Struct Vs Class When To Use Which · Additional notes

3 min read
Mid-level7 min read
Rapid overview

Additional notes

⚙️ 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.


🧩 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)

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.

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.

🧠 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.

See also