C# var Type Inference: When to Use It and When to Avoid
Understand how C# var infers types at compile time, when implicit typing improves readability, and when explicit types are clearer.
The var keyword in C# lets you declare a local variable without spelling out its type. The compiler infers the type from the initializer expression. This is a compile-time feature, not a runtime one: the inferred type is fixed when the code is compiled and recorded in the compiled assembly. For example, var count = 5; compiles to the same code as int count = 5;.
What var Actually Does
When you write var, the compiler looks at the right-hand side of the assignment and determines the static type of the expression. The variable then has that exact type for its entire scope. You cannot assign a value of a different type later, because the variable is not a loosely typed container. This is a compile-time substitution; the generated IL is the same as if you had written the explicit type.
var number = 42; // number is int, not dynamic // number = "text"; // Compile error: cannot implicitly convert string to int
The usual requirement is that the declaration has an initializer: you cannot write var x; and fill in a value later. There are contextual forms such as out var and pattern matching where the compiler infers the type from the surrounding parameter or expression, but the common declaration form needs an initializer. var is not allowed for fields, properties, or method return types.
How Type Inference Works at Compile Time
The compiler performs type inference based on the static type of the initializer. For method calls, the return type is used. For generic methods, the compiler uses the arguments to infer the type parameters. This is especially useful with LINQ, where the result types are often complex and verbose.
var query = customers.Where(c => c.Age > 30).Select(c => c.Name); // The type is IEnumerable<string>, but writing it explicitly would be noisy
Inference is not dynamic dispatch. The variable is strongly typed. If the initializer type is IEnumerable<Customer>, then var gives you exactly that interface type, not a concrete list. This matters when you later call methods that exist only on the concrete type.
When var Improves Readability
var can make code easier to read when the type is obvious from the initializer. For example, var settings = new Dictionary<string, List<int>>(); is clearer than repeating the generic type on both sides. The reader does not need to parse the type twice. This is especially valuable in complex generic expressions where the explicit type adds noise without adding information.
var lookup = new Dictionary<string, List<Order>>(); // vs Dictionary<string, List<Order>> lookup = new Dictionary<string, List<Order>>();
In loops, var is often used with foreach to avoid writing a long type that is already implied by the collection. It also reduces the chance of mismatched types when refactoring, because the variable follows the initializer's type automatically.
When Explicit Types Are Clearer
Explicit typing is better when the type is not obvious from the initializer, especially when the initializer returns an interface, a base class, or a less specific type than what you need. For example, if a method returns IEnumerable<Customer>, using var hides that you are working with an interface. If you later need to call a method specific to List<Customer>, you would have to cast. Writing the explicit type makes the intent clear.
IEnumerable<Customer> customers = GetCustomers(); // explicit var otherCustomers = GetCustomers(); // same type, but less visible
When the initializer is a method call with a non-obvious return type, explicit typing acts as documentation. It can also act as a contract. If GetCustomers() currently returns List<Customer> and you declare the variable as List<Customer>, a future change to IEnumerable<Customer> will produce a compile error at that assignment. With var, the variable would silently become IEnumerable<Customer>, and any List-specific calls later would produce compile errors at those call sites.
var with Anonymous Types and LINQ
Anonymous types have no type name you can write in source code, so var is the only way to declare a variable that holds one. This is common in LINQ projections where you select a subset of properties.
var result = from c in customers select new { c.Name, c.Age }; foreach (var item in result) { Console.WriteLine($"{item.Name}: {item.Age}"); }
The compiler generates a unique internal type for each anonymous type. Using var is required because you cannot name that type. This is a legitimate and necessary use of implicit typing. If you prefer a nameable multi-value type, C# 7 tuple types give you syntax you can write explicitly, for example (string Name, int Age) result = .... For LINQ projections that create anonymous types, var is still required.
Common Misconceptions: var Is Not dynamic
A frequent misunderstanding is that var behaves like dynamic or object. It does not. The variable's type is fixed at compile time. There is no runtime binding, no performance penalty, and no loss of IntelliSense. The compiler treats the variable exactly as if you had written the explicit type. This means you get full compile-time checking, refactoring support, and performance characteristics identical to explicit typing.
dynamic d = 42; // runtime type is int, but compile-time type is dynamic d = "hello"; // allowed, but no compile-time checking var v = 42; // compile-time type is int // v = "hello"; // compile error
Using var does not introduce any runtime overhead. The IL is the same as with an explicit type. The only difference is in the source code, not in the compiled output.
Maintainability and Refactoring with var
var can make refactoring easier because you change the initializer and the variable type follows automatically. For example, if you change a method to return a more derived type, all var variables that use it will automatically see the new type. This can be helpful when you are changing implementations and want to minimize edits.
However, this same behavior can hide breaking changes. If a method's return type changes from List<Customer> to IEnumerable<Customer>, a var variable will silently lose access to List-specific methods. An explicit List<Customer> declaration would cause a compile error, forcing you to address the change. The choice between var and explicit typing is a tradeoff between convenience and explicitness.
A common guideline is to use var when the type is apparent from the initializer and to use explicit types when the type is not obvious or when you want to enforce a specific contract. This keeps the code readable without hiding important information.
Compatibility and Edge Cases
var cannot be used in every situation. Fields, properties, and method return types cannot use it. In the common local declaration form, var requires an initializer with a known type; that is why var x; and var x = null; do not compile.
// var x = null; // compile error: cannot assign null to an implicitly-typed variable
Anonymous types still require var because their type cannot be named in source code. If you want a nameable multi-value type, C# 7 tuple types provide an explicit syntax such as (string Name, int Age) result = ....
When nullable reference types are enabled, var preserves the nullability annotation from the initializer. If a method returns string?, then var s = GetString(); gives s the type string?. This is consistent with the compiler's inference rules and does not introduce unexpected nullability.
In summary, var is a compile-time feature that infers the type from the initializer. It is not dynamic, it has no runtime cost, and it can improve readability when the type is obvious. The key is to use it where it clarifies and avoid it where it hides important type information. The decision should be based on whether the type is evident from the context and whether the variable's type is part of the intended contract.