Caching, Garbage Collection, and System Design: Three .NET Interview Answers
Three questions where the textbook answer is easy and the senior answer is a different thing entirely. Same format each time: definition, cost, scenario, decision.
1. “When would you use caching, and what are the trade-offs?”
Student answer: “Caching stores frequently accessed data in memory to cut database load and improve response times. You can use IMemoryCache or a distributed cache like Redis.”
Engineer answer: “Caching is cheap to add and genuinely tricky, because the failure mode is silent — stale data that looks correct. The hard part is never storage, it’s invalidation. I’ve seen a team cache aggressively for a performance win and then spend a fortnight chasing reports of users seeing outdated values after an edit, because there was no clear invalidation strategy — just TTLs that were too long and no cache-busting on write.”
My defaults: cache read-heavy, rarely-changing data first — reference lists, config, computed rollups. Be explicit about TTL and treat invalidation as a design decision made before the cache goes in, not a patch afterwards. In-process IMemoryCache is fine for a single instance, but the moment you scale out you get per-node divergence, so anything that must be consistent across instances goes in a distributed cache. And I always guard the cache-miss path against a stampede — if a hot key expires under load, a hundred requests shouldn’t all hit the database at once.”
Follow-ups: cache-aside vs write-through; how you’d handle a thundering herd (locking, staggered expiry, background refresh); why caching per-user data often isn’t worth it.
2. “Explain .NET garbage collection.”
Student answer: “The GC is generational. Gen 0 is short-lived objects, gen 1 is a buffer, gen 2 is long-lived. Collecting gen 0 is cheap and frequent; gen 2 is expensive. There’s also a Large Object Heap for objects over 85,000 bytes.”
All correct — and it’s a recital. The interviewer wants to know if GC behaviour has ever shown up in your production metrics.
Engineer answer: “The generational model rests on one assumption: most objects die young. When that assumption holds, the GC is nearly free. It stops being free when you promote a lot of objects to gen 2 that then die anyway — because gen 2 collections are the expensive, potentially blocking ones. In practice the problems I’ve actually seen are: allocation rate so high that gen 0 collections dominate CPU; large short-lived buffers landing on the LOH, which isn’t compacted by default, causing fragmentation and rising memory; and mid-lived objects — things cached for a few minutes — that survive gen 0 and 1 and pile into gen 2.
The fixes aren’t ‘call GC.Collect()’ — that’s almost always wrong. They’re: reduce allocations on hot paths, use ArrayPool / RecyclableMemoryStream for big buffers, prefer Span and struct-based APIs where it matters, and choose Server GC for throughput on multi-core boxes versus Workstation GC for lower latency on smaller ones. I’d confirm any of this with allocation profiling and the GC counters before changing anything.”
Follow-ups: what makes a gen 2 / full collection expensive (it can suspend managed threads); Server vs Workstation and background GC; how IDisposable and finalizers interact with the GC (a finalizer keeps an object alive one extra collection).
3. “Design a URL shortener / a rate limiter / a notification service.”
The junior failure here is jumping straight to boxes and arrows. The senior move is to spend the first two minutes on the questions that change the design:
- Scale: hundreds of requests a day or hundreds of thousands a second? This decides almost everything downstream.
- Read/write ratio: a URL shortener is read-heavy by orders of magnitude — so the design centres on caching and cheap reads, not write throughput.
- Consistency needs: is eventual consistency acceptable? For shortened URLs, yes. For a payment ledger, no.
- Failure tolerance: what happens if a component is down — degrade, queue, or reject?
Then a walkthrough for a URL shortener sounds like: “API in front, a key-generation strategy — base62 of an incrementing ID, or a hash with collision handling — a primary store keyed by short code, a cache in front because reads dominate, and a redirect path that has to be fast and can tolerate a slightly stale mapping. The interesting trade-offs are the key strategy (sequential IDs are guessable and leak volume; random keys need collision checks), and analytics writes (do them async off the redirect path so tracking a click never slows the redirect).”
The interviewer is not grading the diagram. They’re grading whether you drove the requirements before the solution, and whether you named trade-offs instead of asserting one true architecture.
The pattern, one more time
Definition in a sentence. The real cost of getting it wrong. A concrete time it mattered. The rule or decision you now carry. Three very different topics, one structure — and it’s the structure, not the facts, that reads as senior.
Caching starts with an invalidation decision
Before adding a cache, I want an answer to one uncomfortable question: how stale may this value be? If nobody can answer that, the cache policy is not designed yet. A five-minute TTL can be perfectly acceptable for a public catalogue and completely wrong for an authorization decision.
The next question is scope. An in-memory cache belongs to one process. In a multi-instance deployment, each instance can hold a different value. A distributed cache changes that dynamic, but it also adds network calls, serialization and another system that can fail.
GC problems are allocation problems before they are “GC problems”
The garbage collector is usually doing exactly what the application asked it to do. If a hot path creates huge temporary object graphs, frequent collections are a symptom. I would measure allocations first, then look for avoidable churn: repeated large buffers, unnecessary strings, materializing collections too early, or retaining references longer than intended.
System design needs numbers
“Use Redis, a queue and microservices” is not a design. I would rather start with request volume, payload size, latency target, consistency requirements and failure tolerance. Those constraints tell us whether a simple application plus a database is enough or whether another moving part earns its keep.
- What is the expected read/write ratio?
- Which operations must be strongly consistent?
- What can be retried safely?
- Where can backpressure build up?
- What happens when a dependency is unavailable for ten minutes?
Every core .NET interview topic, written in this exact format with weak-versus-strong answers side by side, is .NET Job Interview OS — an A4-printable prep system covering .NET foundations, ASP.NET Core internals, EF Core trade-offs and system design.
The takeaway
Caching, GC, and system design questions all reward the same thing: showing you’ve seen the failure, not just read the definition. Rehearse each answer as definition → cost → scenario → decision, and the topic almost stops mattering.