Interview Prep — The Architect's Final Blueprint
25 XPHard
Ctrl+Enter RunCtrl+S Save
🎓 C#/.NET Interview Masterclass
You've built the entire .NET city from the ground up. Now it's time to walk into that interview room and own it. This lesson covers the most-asked, highest-impact concepts that senior interviewers love to probe.
🏗️ SOLID Principles
The five pillars of maintainable object-oriented design:
S — Single Responsibility: A class should have one, and only one, reason to change.
O — Open/Closed: Open for extension, closed for modification. Use inheritance/interfaces to add behavior.
L — Liskov Substitution: Subtypes must be substitutable for their base types without breaking correctness.
I — Interface Segregation: Many small, specific interfaces are better than one large, general-purpose one.
D — Dependency Inversion: Depend on abstractions (interfaces), not concrete implementations.
🎨 Essential Design Patterns
Singleton Pattern
Ensures a class has exactly one instance and provides a global point of access. Common use: configuration managers, logging services, connection pools.
public sealed class Singleton
{
private static readonly Lazy<Singleton> _instance =
new Lazy<Singleton>(() => new Singleton());
private Singleton()
{
Console.WriteLine("Singleton instance created");
}
public static Singleton Instance => _instance.Value;
}
Factory Pattern
Creates objects without exposing creation logic. The caller works with an interface, and the factory decides which concrete class to instantiate.
interface IBuilding { string Name { get; } }
class Office : IBuilding { public string Name => "Office"; }
class Residence : IBuilding { public string Name => "Residence"; }
class BuildingFactory
{
public static IBuilding Create(string type) => type switch
{
"office" => new Office(),
"residence" => new Residence(),
_ => throw new ArgumentException($"Unknown type: {type}")
};
}
Observer Pattern
Defines a one-to-many dependency: when one object changes state, all dependents are notified. In C#, event and delegate are built-in observer mechanisms.
class CityAlert
{
public event Action<string> OnAlert;
public void RaiseAlert(string msg) => OnAlert?.Invoke(msg);
}
// Subscribe
var alert = new CityAlert();
alert.OnAlert += msg => Console.WriteLine($"Fire dept: {msg}");
alert.OnAlert += msg => Console.WriteLine($"Police: {msg}");
alert.RaiseAlert("Emergency!");
💉 Dependency Injection (DI)
Instead of a class creating its own dependencies, they are injected from outside (usually via constructor). This makes code testable, flexible, and follows the Dependency Inversion Principle.
interface ILogger { void Log(string msg); }
class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine(msg);
}
class BuildingService
{
private readonly ILogger _logger;
public BuildingService(ILogger logger) => _logger = logger;
public void Build() => _logger.Log("Building constructed!");
}
// Injection
var service = new BuildingService(new ConsoleLogger());
service.Build();
⚡ Async Best Practices
Always use async Task, never async void (except event handlers).
Use ConfigureAwait(false) in library code to avoid deadlocks.
Prefer ValueTask for hot paths where the result is often synchronous.
Use CancellationToken to support cooperative cancellation.
Avoid .Result and .Wait() — they block and can deadlock.
🧠 Memory Management
Stack: Fast, LIFO. Stores value types and method frames. Auto-cleaned when scope exits.
Heap: Managed by GC. Stores reference type instances. Three generations (0, 1, 2) + LOH.
Span<T>: Stack-only type for slicing arrays/memory without allocations.
ArrayPool<T>: Rent and return arrays to avoid GC pressure.
IDisposable: Deterministic cleanup of unmanaged resources.
🧵 Threading Essentials
Thread — OS-level thread. Heavyweight. Avoid creating manually in modern C#.
ThreadPool — Reuses threads. Task.Run queues work to the pool.
lock — Mutual exclusion. Uses Monitor under the hood.
SemaphoreSlim — Limits concurrent access to a resource (async-friendly).
ConcurrentDictionary<K,V> — Thread-safe dictionary without explicit locking.
Interlocked — Atomic operations (increment, compare-and-swap) without locks.
📝 Quick-Fire Interview Answers
struct vs class? — Struct is a value type (stack), class is a reference type (heap). Structs are copied on assignment.
abstract vs interface? — Abstract classes can have implementation; interfaces (pre-C# 8) are pure contracts. C# 8+ allows default interface methods.
sealed keyword? — Prevents inheritance. Also optimizes virtual dispatch (JIT can devirtualize calls).
ref vs out? — Both pass by reference. out must be assigned inside the method; ref must be initialized before the call.
string vs StringBuilder? — Strings are immutable; concatenation creates new objects. Use StringBuilder for many concatenations.
📋 Instructions
**The Architect's Singleton Blueprint**
Demonstrate the Singleton pattern:
1. Create a `sealed class CityManager` with:
- A `private static CityManager _instance` field (initially null)
- A private constructor that prints `"Singleton instance created"`
- A public static `Instance` property that creates the instance on first access (lazy initialization)
2. In `Main`:
- Access `CityManager.Instance` and store in `mgr1`
- Access `CityManager.Instance` again and store in `mgr2`
- Print `"Same instance: {result}"` where result is `mgr1 == mgr2` (should be `True`)
- Print `"Pattern: Singleton ensures one instance"`
- Print `"Ready for interviews!"`
Make the constructor private and print the creation message. The static Instance property should check if _instance is null, and if so, create it. Both mgr1 and mgr2 will point to the same object, so `mgr1 == mgr2` evaluates to True.