Memory Allocation and Management: Reference Types, Value Types, Arrays, and Linked Lists
Reference types vs. value types

Class vs. Struct: Memory Behavior
Class Example
When using classes, a reference to the object is stored on the stack, but the actual object itself is stored in the heap. For structs, since they are value types, the entire object is stored on the stack if declared within a method.
In the class example, only the reference is copied, so both a() and b() point to the same object in the heap. In the struct example, the entire object is copied, so a() and b() are independent.

Class and Struct Example with Person
Class
Struct

For classes, both a() and b() refer to the same object in the heap. For structs, a() and b() are independent objects, with the contents of a() copied into b().
Arrays
An array is a collection of elements, all of the same type, stored contiguously in memory. Contiguous means that the elements are placed next to each other in memory, with no gaps between them. Homogeneous means that every element in the array is of the same data type. Elements in an array can be accessed using an index.
Actual layout of an array in memory

Linked List
A linked list is a collection of nodes, where each node stores a value (of a homogeneous data type) and a reference (or pointer) to the next node. Unlike arrays, nodes are stored non-contiguously in memory; their locations may be scattered, with the references connecting them.

Exceptions: Misleading Simplifications
Note the following exceptions:
Value-type fields within reference-type objects are allocated on the heap.
Captured locals in closures might be moved to the heap.
Example of a closure capturing a local variable:
Garbage Collection in .NET
In .NET, garbage collection (GC) is responsible for automatically managing memory. The GC monitors which objects in the heap are no longer being referenced and frees up that memory. In modern object-oriented environments, almost every aspect of execution has been optimized for speed. As a result, if your application experiences performance bottlenecks that aren't caused by your own code, they are most likely due to garbage collection. This is especially true when dealing with large numbers of objects or frequent memory allocations and deallocations.
In recent versions, Microsoft has been focusing on reducing the impact of garbage collection by introducing mechanisms like Span<T>() and Memory<T>(), which allow working with memory in a more efficient way, minimizing heap allocations. This helps improve performance by reducing the frequency and cost of garbage collection.
Queue: First In, First Out (FIFO)

When implementing a Queue using a linked list, adding elements to the back (enqueue) and removing from the front ( dequeue) are highly efficient O(1) operations. This is because the linked list maintains references to both the front and back nodes, allowing direct access for these operations.
While a Queue could be implemented using an array, this approach presents challenges:
The array requires pre-allocated size
Dequeuing elements from the front would require shifting all remaining elements forward
Array resizing becomes necessary when capacity is reached
Stack: Last In, First Out (LIFO)

A linked list implementation of a Stack is particularly efficient for push and pop operations, both being O(1), as they only involve manipulating the head of the list. No element shifting is required, unlike with array-based implementations.
Similar to Queue, an array-based implementation of Stack would face the same challenges with pre-allocated size and array resizing.
Garbage Collection Performance Summary (Optional)
From C# 12 in a Nutshell by Joseph Albahari (2024, pp. 593-597):
Generation | Object Size/Type | Collection Time | When It Occurs |
|---|---|---|---|
Gen 0 | Newest objects, typically small and short-lived | Less than 1 millisecond | Most frequent, typically unnoticeable |
Gen 1 | Objects that survived a Gen 0 collection | A few milliseconds | Intermediate frequency |
Gen 2 | Long-lived objects that survived Gen 1 | Part of full collection | Least frequent |
LOH (Large Object Heap) | Large objects ≥ 85,000 bytes | Part of full collection | Infrequent, special rules apply |
Full collection | All generations (0, 1, 2, and LOH) | Up to 100 milliseconds | Least frequent, can cause noticeable pauses |
The generational design significantly improves performance by focusing collection efforts on the newest objects, which are the most likely to become garbage. Each generation gets collected at different frequencies, creating an efficient balance between memory reclamation and application performance.

See Also:
Exploring Performance with New Collection Expression Syntax in C# 12 ( mentions
Span<T>)Utilizing Record Structs for Enhanced Performance in .NET ( struct performance)