Once I asked myself a question that stopped me for a second: "Why don't we just make everything dynamic? Then we never have to worry about types at all."

I knew var, object, and dynamic were different, I used var constantly and avoided dynamic out of some vague instinct...but I couldn't actually explain why in a way that would convince me.

They Look Similar. They Are Not the Same Thing.

All three let you write code without explicitly naming a concrete type on the left-hand side, which is exactly why they get confused:

var a = "hello";
object b = "hello";
dynamic c = "hello";

All three compile. All three run. All three currently hold a string. But they behave completely differently the moment you do anything interesting with them, because each one represents a different stage at which C# figures out the type.

var

This is the one people misunderstand most. var is not "no type." It's not JavaScript's var. The compiler looks at the right-hand side, figures out the concrete type, and locks it in permanently at compile time.

var name = "Alice";      // compiler infers string
var count = 5;            // compiler infers int
var product = GetProduct(); // compiler infers whatever GetProduct() returns

name = 42; // ERROR — name is a string, always was, always will be

This matters for performance too: since the type is fully known at compile time, there's zero runtime overhead.

object: Everything's Base Type, With a Boxing Tax

object is the ancestor of every type in C#, every class, every struct, everything. So you can store anything in an object variable. The catch is that once it's in there, the compiler only knows about the object members (ToString(), Equals(), GetHashCode(), GetType()), nothing else.

object o = "hello";
int length = o.Length; // ERROR — object has no Length property

Even though the runtime value is clearly a string, the compiler refuses to let you call .Length on it, because as far as compile time type checking is concerned, o is just an object. To get the string back, you need an explicit cast:

object o = "hello";
string s = (string)o;
int length = s.Length; // fine now

The Boxing Problem

When you put a value type into an object, C# has to "box" it, wrap the value type in a heap-allocated object so it can be referred to by reference.

int number = 42;
object boxed = number; // BOXING: number gets copied onto the heap
int unboxed = (int)boxed; // UNBOXING: copied back onto the stack

Boxing isn't free. Every box is a heap allocation, which means garbage collection pressure.

dynamic

dynamic looks like var but does the opposite thing. Instead of the compiler pinning down a type early, it skips type checking altogether and figures things out at runtime, using something called the Dynamic Language Runtime (DLR).

dynamic thing = "hello";
Console.WriteLine(thing.Length); // works, resolved at runtime

thing = 42;
Console.WriteLine(thing.Length); // compiles fine... then throws at runtime!

Both lines compile without complaint, because the compiler isn't checking anything about thing's members, it just trusts that whatever method or property you call will exist when the program actually runs.

dynamic doesn't make types disappear. It just moves the moment you discover a type error from "while you're typing" to "while a customer is using the app."

Where dynamic genuinely earns its keep

It has real, legitimate uses:

Side-by-Side, So It Actually Sticks

var

object

dynamic

Type resolved

Compile time (inferred)

Compile time (as object)

Runtime

Requires initializer

Yes

No

No

Type errors caught

Compile time

Compile time (via cast)

Runtime only

Value types get boxed?

No (retains real type)

Yes, when assigned

Yes, when needed

Performance

No overhead

Boxing overhead for value types

Slower (DLR resolution each call)

So Which One Do You Actually Reach For?