Sometimes a value type needs to be treated as an object — passed to an API that only accepts object, or stored in a non-generic collection. That requires boxing: wrapping the value in a heap-allocated box.
int number = 42;
object boxed = number; // boxing: allocates a heap object wrapping 42
int unboxed = (int)boxed; // unboxing: copies the value back out
Boxing isn't free — it's a heap allocation plus a copy, and it happens more often than people expect, including implicitly:
// Implicit boxing — easy to miss because there's no visible cast
Console.WriteLine("Value: " + 42); // 42 (an int) is boxed to be treated as an object for string concatenation
// A classic hidden-boxing bug: adding int values to a non-generic ArrayList
ArrayList list = new ArrayList();
list.Add(1); // boxes 1
list.Add(2); // boxes 2
int sum = (int)list[0] + (int)list[1]; // unboxes both to add them
This is exactly why generics matter for performance, not just type safety: List<int> stores real int values directly in a contiguous array, with zero boxing — a non-generic ArrayList typed to hold object boxes every single value type inserted into it, and the allocation and GC pressure from that adds up fast in a hot loop.