Previously, in the roadmap post, we mapped the whole .NET interview loop and saw that interviewers reward understanding over recitation. Now we get to work. This part covers the five C# language questions that appear in nearly every technical round — and, more importantly, the understanding that survives the follow-ups.
The five questions are: value types versus reference types (the answer is copy semantics, not stack versus heap), why strings are immutable (and why StringBuilder exists), what boxing is and where it hides, interface versus abstract class (a contract versus a shared base), and what records add over classes (value equality, with-expressions, immutability by default). The common thread: semantics beat storage details, and "why" beats "what."
"What's the difference between a value type and a reference type?"
Why they ask it: This is the single most common C# question, and it's a trap for the memorizers. Everyone can recite "stack vs heap." The interviewer is checking whether you know what that recitation misses — the semantics — because those semantics cause real bugs.
A strong answer: The honest version first: where things are stored (stack vs heap) is an implementation detail, and it's not even always true — a value type field inside a class lives on the heap with its owner. What actually matters is copy semantics. Assigning a value type copies the data itself, so you get two independent values. Assigning a reference type copies a reference, so two variables now point at one shared object. Every surprise in this area traces back to that difference:
struct PointStruct { public int X; }
class PointClass { public int X; }
var s1 = new PointStruct { X = 1 };
var s2 = s1; // full copy
s2.X = 99;
Console.WriteLine(s1.X); // 1 — s1 is untouched
var c1 = new PointClass { X = 1 };
var c2 = c1; // same object, two names
c2.X = 99;
Console.WriteLine(c1.X); // 99 — "spooky action" if you expected a copy
Lead with semantics, mention storage as the footnote, and you've flipped the usual answer on its head — in a way interviewers notice.
Try it: Paste the code above into your console app. Then change PointStruct to store its X in a class field's property and predict, before running, which lines print 1 and which print 99. Being able to predict this cold is the skill being tested.
Follow-ups to expect:
- "Is a string a value type or a reference type?" — Reference type, but immutable, so it feels like a value.
- "What happens when you pass a struct to a method?" — It's copied, so mutations inside the method don't leak out (unless you use
ref). - "Where does a struct field of a class live?" — On the heap, inside the object — proof that "structs live on the stack" is a simplification.
Red flag: Answering only "value types go on the stack, reference types go on the heap" and stopping. It's the memorized line, it's incomplete, and the very first follow-up will find that out.
"Why are strings immutable, and what does that imply?"
Why they ask it: It sounds like trivia, but it tests whether you connect a language design decision to its practical consequences — the "why, not just what" signal from Part 1.
A strong answer: Once a string is created, it can never change; every "modification" produces a brand-new string. This buys safety — strings can be shared across threads, used as dictionary keys, and passed around freely without anyone mutating them behind your back. It also enables interning: identical string literals can share one instance because nobody can alter it.
The implication interviewers want to hear: concatenation in a loop is quietly expensive,
because each pass allocates a whole new string and copies everything so far. That's the
job StringBuilder exists for — it maintains a mutable buffer and builds the
string once at the end.
var s = "";
for (var i = 0; i < 10_000; i++)
{
s += i; // ~10,000 allocations, each copying the whole string
}
var sb = new StringBuilder();
for (var i = 0; i < 10_000; i++)
{
sb.Append(i); // one growing buffer
}
var result = sb.ToString();
Try it: Wrap both loops above in Stopwatch timings and crank the count to 100,000. The gap isn't subtle — and now you have a number to quote in interviews instead of a vague "it's slower."
Follow-ups to expect:
- "So is a handful of + concatenations bad?" — No; the compiler and runtime handle small cases fine. It's loops that hurt.
- "What is string interning?" — The runtime keeps a pool of unique strings so identical literals share one instance.
- "Why are immutable types nice for dictionary keys?" — The hash code can't change while the key sits in the dictionary.
Red flag: "You should always use StringBuilder instead of +." Always-rules signal cargo-culting; the real answer is about when allocation cost compounds.
"What is boxing, and when does it bite?"
Why they ask it: Boxing sits exactly on the seam between value and reference types, so it confirms you really understood question one — and it tests whether you can spot hidden allocations in innocent-looking code.
A strong answer: Boxing is what happens when a value type is converted to
object (or to an interface it implements): the runtime allocates a box on the
heap and copies the value into it. Unboxing casts it back out. Each box is a small heap
allocation — harmless once, painful a million times in a hot loop.
int n = 42;
object o = n; // boxed: heap allocation, value copied in
int back = (int)o; // unboxed: value copied back out
Where it bites: the old non-generic collections like ArrayList and
Hashtable store object, so every int you add gets boxed — which
is a big part of why generics were added and why List<int> stores ints
with no boxing at all. Modern bites are sneakier: string formatting that takes
object parameters, or calling an interface method through an interface-typed
variable that holds a struct.
Try it: Add an int to an ArrayList and to a List<int>, then read both back. The ArrayList makes you cast — that cast is the unboxing, visible in your own code. The generic list never needed the box.
Follow-ups to expect:
- "How would you find boxing in a real codebase?" — A profiler showing allocation hot spots, or analyzer warnings.
- "Does putting a struct into an interface-typed variable box it?" — Yes; the interface reference needs a heap object to point at.
- "Why did generics largely solve this?" —
List<int>is specialized for int, so values are stored directly.
Red flag: Defining boxing correctly but being unable to name a single place it actually occurs. The definition is the flashcard; the examples are the understanding.
"Interface vs abstract class — when would you use each?"
Why they ask it: This is a design-judgment question wearing a trivia costume. Listing feature differences is table stakes; the interviewer wants to hear you reason about intent.
A strong answer: An interface declares a contract:
"anything that can do X." An abstract class provides a shared base:
"these things are all a kind of Y, and here's the common machinery." A class can implement
many interfaces but inherit only one base class, which is itself a design hint — capabilities
are many, identity is singular. So: use an interface when unrelated types need a common
capability (IComparable, IDisposable); use an abstract class when
a family of types shares real implementation and state.
Do mention that interfaces can now have default method implementations — it shows your knowledge is current — but immediately add that this doesn't erase the design distinction. Default interface methods are mainly for evolving published APIs without breaking implementers; an abstract class is still the natural home for shared state and protected helpers. The design answer, not the feature list, is what they're grading.
Try it: Sketch a payment system with CardPayment, BankTransfer, and WalletPayment. Decide what belongs in an IPaymentMethod interface versus a PaymentBase abstract class (shared validation? audit logging?). There's no single right split — practicing the justification is the exercise.
Follow-ups to expect:
- "Why might you prefer an interface even when an abstract class would work?" — Looser coupling and far easier testing/mocking.
- "When have default interface methods helped you?" — Adding a member to a widely-implemented interface without breaking everyone.
- "Can an abstract class have a constructor?" — Yes; derived classes call it, even though you can't instantiate the abstract class directly.
Red flag: "An interface can't have any implementation" — outdated for years now — or reciting a feature-difference table with no opinion about when you'd choose either.
"What do records give you over classes?"
Why they ask it: Records are the modern-C# checkpoint: the question separates people who've kept up from people whose C# froze a decade ago. It also loops back — again — to value-versus-reference semantics.
A strong answer: A record is a class (or struct) with value-based behavior generated for you. Three headline gifts: value equality — two records with equal contents are equal, whereas two class instances are only equal if they're the same object; with-expressions — non-destructive mutation that copies a record with some properties changed; and immutability by default via concise positional syntax and init-only properties.
public record Money(decimal Amount, string Currency);
var a = new Money(10m, "USD");
var b = new Money(10m, "USD");
Console.WriteLine(a == b); // True — value equality
var c = a with { Amount = 25m }; // new record, a is unchanged
Console.WriteLine(a.Amount); // 10
Just as important: knowing when not to use them. Entities with an identity and a
mutable lifecycle — an EF Core Customer whose row is tracked by primary key —
fit classes better, because there value equality is actively wrong: two snapshots of the
same customer at different times shouldn't compare equal, and ORMs track identity, not
contents. Records shine for values: money, coordinates, DTOs, configuration.
Try it: Recreate Money as a plain class with the same two properties and compare two equal-valued instances with == and Equals. Watching the class say "not equal" where the record said "equal" makes the whole feature click.
Follow-ups to expect:
- "Are records immutable?" — By convention and default syntax, yes; but you can declare mutable properties, so it's a default, not a law.
- "What does a with-expression actually do?" — Calls a generated clone, then applies your property overrides to the copy.
- "Record class vs record struct?" — Record structs add value-type copy semantics on top of the value equality.
Red flag: "Records are just shorter syntax for classes." That misses the entire point — the equality and immutability semantics — and suggests the candidate has read about records but never used one.
Frequently asked
- What is the difference between a value type and a reference type in C#?
- What matters is copy semantics, not storage. Assigning a value type copies the data itself, so you get two independent values; assigning a reference type copies a reference, so two variables point at one shared object and a change through one is visible through the other. Stack versus heap is an implementation detail and not even always true: a value type field inside a class lives on the heap with its owner.
- Why are strings immutable in C#, and what does that imply?
- Once a string is created it never changes; every modification produces a new string. That makes strings safe to share across threads and use as dictionary keys, and it enables interning, where identical literals share one instance. The practical implication is that concatenating in a loop allocates a whole new string every pass, which is the job StringBuilder exists for; a handful of concatenations outside a loop is fine.
- When should you use an interface versus an abstract class in C#?
- An interface declares a contract: anything that can do X. An abstract class provides a shared base with common implementation and state. A class can implement many interfaces but inherit only one base class, so use an interface when unrelated types need a common capability and an abstract class when a family of types shares real implementation. Interfaces can have default method implementations now, mainly for evolving published APIs, but that does not erase the design distinction.
- What do records give you over classes in C#?
- A record is a class or struct with value-based behavior generated for you: value equality, so two records with equal contents are equal; with-expressions for non-destructive copies with some properties changed; and immutability by default through positional syntax and init-only properties. Records suit values like money, coordinates, DTOs and configuration; entities with an identity and a mutable lifecycle, such as an EF Core entity tracked by primary key, fit classes better.
Where to next
Five questions, one common thread: semantics beat storage details, and "why" beats "what." If C# fundamentals still feel shaky in places, my post on why C# is a great first language is a gentler on-ramp to the same ideas.
Next we hit the round that produces more interview sweat than any other: Part 3 — async/await Interview Questions (the Deadlock Special).