There's this customary interview question in the .NET world that's usually asked when interviewing junior developers and it goes like this:

What's the difference between value and reference types?

And the most common given answer is:

Value types are kept on the stack whereas reference types are kept on the heap. When sending an argument to a method, value types are passed by value and reference types are passed by reference. [Followed by a simple example of passing an integer versus passing an instance of a class]

This usually is enough to satisfy an interviewer considering it's a question usually asked to juniors, but in reality both parts of the answer were wrong. Whether most interviewers are unaware of this or whether they just don't care because they're interviewing an inexperienced person is something that will remain a mystery. The reasons why the given answer is wrong are a bit technical, but because I'm very pedantic about these things I will dive into them in this post.

So let's start with the first part:

Value types are kept on the stack whereas reference types are kept on the heap.

The problem with this answer is that is it wrong in so many scenarios. Both value and reference types are sometimes on the stack and sometimes on the heap, and the rules that govern the outcome are complicated enough that we shouldn't be making generalized assumptions on where something is allocated. If you want to get a clearer picture of these different scenarios take a look at (our C# lord) Jon Skeet's writings on memory in .NET. Moreover, I think we can trust Eric Lippert who was a Principal Developer at Microsoft on the C# compiler team and a member of the C# language design team, saying that If the relevant thing [about reference and value types] was their allocation details then we’d have called them “heap types” and “stack types” - in his The Stack Is An Implementation Detail articles on MSDN. I encourage you to read both parts as they give an insider's view of this topic.

Now that we've sorted that, let's take a look at the second part of the answer, which I feel is more important.

When sending an argument to a method, value types are passed by value and reference types are passed by reference.

The default way of passing parameters in C# is by value, for both value and reference types.

But changes to reference types reflect in the caller scope whereas changes to value types do not, so where does that behavior come from? Well it's a problem of terminology. What passing by value means is that we're passing a copy of a variable, where the key thing is what that variable itself holds - which is dependent on whether it's a value or a reference type. Let's look at an example:

var x = 5;
var y = new User();

In the code above the variable x holds the value 5, but the variable y does not hold an instance of the User class. Rather, the object is created in a different place and y just holds the reference to that object. An important distinction! When we're passing a variable to a method we're passing a copy of that variable, the same with both x and y. So the mechanism of passing is the same, but what is being passed is different. Herein lies the difference in behavior between value and reference types. What is being passed as opposed to how it's being passed. Now fixing the original answer just takes a bit of paraphrasing:

When sending an argument to a method, when it comes to value types we're passing a value and when it comes to reference types we're passing a reference.

There's a term called "pass/call by sharing" which describes this passing of a reference by value in an attempt to distinguish it from actual passing by reference, but this term is not in common use so don't expect anyone to understand you if you use it.

And if you're still unsure about the point I'm making, look at this example:

void ChangeUser(User localUser) {
	localUser = null;
}

var user = new User();
ChangeUser(user);

if (user == null) {
	Console.WriteLine("We changed the user");
}

Have we changed the user reference?

No. Because in the ChangeUser() method we only set the local copy of the reference to point to null, not the reference in the caller scope. So although we can use the local reference to make changes to the object it's pointing to, we can't alter the original user reference in the caller scope - because we're working with a separate local copy! That's what passing by value is.

So what is actually passing by reference?

Generally, passing by reference is not a common practice in modern programming. In C# you can achieve it with the ref and out keywords that I'm sure you've come across before. When you pass a reference type using the ref keyword you are actually passing the original reference, not a copy of it. So in term you can make that reference point to something else, which we couldn't do in the previous example.

But what happens when we pass a value type with the ref keyword? Well as one would expect, altering its value in a method will change it in the caller scope as well. At this point one may wonder whether passing a reference type by value and passing a value type by reference (using the ref keyword) produce the same outcome. And the answer to that riddle is no. Remember, reference type variables are a bit special in the fact that they not only hold a reference as a value, but that value is basically an address to some object in some place. So you can use reference type variables in two ways: 1) you can change the object that they point to; 2) you can change the reference itself to point to a different object or null. Because of this, when passing a reference by value, if you re-initialize the reference in the method scope to make it point to something else, the original reference in the caller scope won't change (it continues to point to the same object). This doesn't happen with value types. Whatever you set a value type to be after passing it by reference will always reflect in the caller scope.

So I hope that was a good deconstruction of what I've found to be not quite common knowledge.