πŸ“„

Csharp Interview Qa

Intermediate 5 min read 900 words

C# Interview Questions & Answers

Introduction

This comprehensive guide covers the most common C# and .NET interview questions, organized by topic and difficulty level. Each answer includes explanations and code examples.


Table of Contents


C# Fundamentals

1. What is the difference between value types and reference types?

Answer:

Aspect Value Types Reference Types
Storage Stack (usually) Heap
Contains Actual data Reference to data
Assignment Copies value Copies reference
Default 0, false, etc. null
Examples int, struct, enum class, interface, delegate
// Value type - copies the value
int a = 5;
int b = a;
b = 10;
Console.WriteLine(a);  // 5 (unchanged)

// Reference type - copies the reference
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;
list2.Add(4);
Console.WriteLine(list1.Count);  // 4 (both point to same object)

2. What is boxing and unboxing?

Answer: Boxing converts a value type to object (reference type). Unboxing extracts the value type from the object.

// Boxing: value type β†’ object (heap allocation)
int number = 42;
object boxed = number;  // Boxing occurs

// Unboxing: object β†’ value type (requires cast)
int unboxed = (int)boxed;  // Unboxing occurs

// Performance impact
for (int i = 0; i < 1000000; i++)
{
    object obj = i;      // Boxing: 1M heap allocations!
    int val = (int)obj;  // Unboxing
}

// Avoid with generics
List<int> list = new List<int>();  // No boxing
list.Add(42);

When it happens:

  • Passing value type to object parameter
  • Storing value type in non-generic collection
  • Calling object methods on value types (ToString, GetHashCode on structs)

3. What is the difference between const and readonly?

Answer:

Feature const readonly
Initialization Compile time Runtime (constructor)
Implicit static Instance or static
Types allowed Primitives, strings Any type
Memory Compiled into IL Field in type
public class Configuration
{
    // const: Compile-time constant, value baked into callers
    public const double Pi = 3.14159;

    // readonly: Runtime constant, can use constructor
    public readonly DateTime StartupTime;
    public readonly string ConnectionString;

    public Configuration(string connStr)
    {
        StartupTime = DateTime.Now;  // Set at runtime
        ConnectionString = connStr;   // From parameter
    }
}

// Static readonly: One value for all instances
public static readonly Guid AppId = Guid.NewGuid();

4. What are nullable value types and nullable reference types?

Answer:

// Nullable value types (since C# 2.0)
int? nullableInt = null;
int? anotherInt = 42;

// Check and access
if (nullableInt.HasValue)
    Console.WriteLine(nullableInt.Value);

// Null-coalescing
int result = nullableInt ?? 0;

// Null-conditional
int? length = nullableInt?.ToString().Length;

// Nullable reference types (C# 8.0+, opt-in)
#nullable enable
string nonNullable = "hello";    // Cannot be null
string? nullable = null;         // Can be null

// Compiler warnings
// nonNullable = null;           // Warning!
Console.WriteLine(nullable.Length);  // Warning: possible null

// Null-forgiving operator
Console.WriteLine(nullable!.Length);  // "Trust me, it's not null"

5. Explain the ref, out, and in keywords.

Answer:

// ref: Pass by reference, must be initialized before call
void Increment(ref int value)
{
    value++;
}
int x = 5;
Increment(ref x);  // x is now 6

// out: Pass by reference, must be assigned in method
bool TryParse(string input, out int result)
{
    return int.TryParse(input, out result);
}
if (TryParse("42", out int number))
    Console.WriteLine(number);

// in: Pass by reference, readonly (no modification allowed)
void PrintLength(in string text)
{
    Console.WriteLine(text.Length);
    // text = "new";  // Error: cannot modify
}

// ref readonly return (C# 7.2)
ref readonly int GetLargest(in int[] array)
{
    int maxIndex = 0;
    for (int i = 1; i < array.Length; i++)
        if (array[i] > array[maxIndex])
            maxIndex = i;
    return ref array[maxIndex];
}

Object-Oriented Programming

6. What is the difference between abstract class and interface?

Answer:

Feature Abstract Class Interface
Inheritance Single Multiple
Fields Can have Cannot (until C# 8)
Constructors Can have Cannot
Access modifiers Any Public by default
Implementation Can provide Default impl (C# 8+)
Use case β€œis-a” relationship β€œcan-do” capability
// Abstract class: shared implementation + contract
public abstract class Animal
{
    protected string Name { get; set; }

    public Animal(string name) => Name = name;

    public abstract void MakeSound();  // Must implement

    public virtual void Sleep()  // Can override
    {
        Console.WriteLine($"{Name} is sleeping");
    }
}

// Interface: capability contract
public interface ISwimmable
{
    void Swim();

    // Default implementation (C# 8.0+)
    void Float() => Console.WriteLine("Floating...");
}

public class Duck : Animal, ISwimmable
{
    public Duck(string name) : base(name) { }

    public override void MakeSound() => Console.WriteLine("Quack!");
    public void Swim() => Console.WriteLine($"{Name} is swimming");
}

7. Explain the SOLID principles.

Answer:

S - Single Responsibility: A class should have only one reason to change.

// ❌ Bad: Multiple responsibilities
public class User
{
    public void Save() { /* database logic */ }
    public void SendEmail() { /* email logic */ }
}

// βœ… Good: Separated responsibilities
public class User { /* user data only */ }
public class UserRepository { public void Save(User user) { } }
public class EmailService { public void SendEmail(User user) { } }

O - Open/Closed: Open for extension, closed for modification.

// βœ… Good: Extend via inheritance/composition, don't modify existing
public abstract class Shape { public abstract double Area(); }
public class Circle : Shape { public override double Area() => Math.PI * Radius * Radius; }
public class Rectangle : Shape { public override double Area() => Width * Height; }

L - Liskov Substitution: Subtypes must be substitutable for base types.

// ❌ Bad: Square violates Rectangle contract
public class Rectangle { public virtual int Width { get; set; } public virtual int Height { get; set; } }
public class Square : Rectangle
{
    public override int Width { set { base.Width = base.Height = value; } }
}

// βœ… Good: Use composition or separate hierarchy

I - Interface Segregation: Clients shouldn’t depend on interfaces they don’t use.

// ❌ Bad: Fat interface
public interface IWorker { void Work(); void Eat(); void Sleep(); }

// βœ… Good: Segregated interfaces
public interface IWorkable { void Work(); }
public interface IFeedable { void Eat(); }

D - Dependency Inversion: Depend on abstractions, not concretions.

// ❌ Bad: Direct dependency
public class OrderService
{
    private SqlDatabase _database = new SqlDatabase();  // Concrete!
}

// βœ… Good: Depend on abstraction
public class OrderService
{
    private readonly IRepository _repository;
    public OrderService(IRepository repository) => _repository = repository;
}

8. What is the difference between method overloading and overriding?

Answer:

public class Calculator
{
    // Overloading: Same name, different parameters (compile-time polymorphism)
    public int Add(int a, int b) => a + b;
    public double Add(double a, double b) => a + b;
    public int Add(int a, int b, int c) => a + b + c;
}

public class Animal
{
    // Virtual method - can be overridden
    public virtual void Speak() => Console.WriteLine("...");
}

public class Dog : Animal
{
    // Overriding: Same signature in derived class (runtime polymorphism)
    public override void Speak() => Console.WriteLine("Woof!");
}

// new keyword - hides base method (not recommended)
public class Cat : Animal
{
    public new void Speak() => Console.WriteLine("Meow!");
}

Animal animal = new Dog();
animal.Speak();  // "Woof!" - override works

Animal cat = new Cat();
cat.Speak();  // "..." - new hides, doesn't override!

Collections and Generics

9. What is the difference between IEnumerable<T> and IQueryable<T>?

Answer:

Feature IEnumerable IQueryable
Execution In-memory Remote (database)
Expression Delegate Expression tree
Evaluation Client-side Server-side
Use case LINQ to Objects LINQ to SQL/EF
// IEnumerable: All data loaded to memory, filtered in C#
IEnumerable<User> users = dbContext.Users.ToList();  // Loads ALL users
var filtered = users.Where(u => u.Age > 18);  // Filters in memory

// IQueryable: Filter translated to SQL, executed on server
IQueryable<User> query = dbContext.Users;  // No execution yet
var filtered = query.Where(u => u.Age > 18);  // Adds to expression tree
var result = filtered.ToList();  // NOW executes: SELECT * WHERE Age > 18

// Danger: Switching to IEnumerable loses query optimization
var bad = dbContext.Users
    .AsEnumerable()  // Forces in-memory from here
    .Where(u => u.Name.StartsWith("A"));  // This runs in C#, not SQL!

10. Explain covariance and contravariance.

Answer:

// Covariance (out): Can use more derived type as less derived
// "Producer" - only outputs T
interface IProducer<out T> { T Produce(); }

IProducer<Dog> dogProducer = new DogFactory();
IProducer<Animal> animalProducer = dogProducer;  // βœ… Dog is Animal

// Contravariance (in): Can use less derived type as more derived
// "Consumer" - only inputs T
interface IConsumer<in T> { void Consume(T item); }

IConsumer<Animal> animalConsumer = new AnimalHandler();
IConsumer<Dog> dogConsumer = animalConsumer;  // βœ… Can handle any Animal

// Built-in examples
IEnumerable<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;  // IEnumerable<out T> is covariant

Action<Animal> feedAnimal = a => a.Eat();
Action<Dog> feedDog = feedAnimal;  // Action<in T> is contravariant

LINQ

11. What is deferred execution in LINQ?

Answer: LINQ queries are not executed when defined; they execute when enumerated.

var numbers = new List<int> { 1, 2, 3, 4, 5 };

// Query defined but NOT executed
var query = numbers.Where(n => n > 2);

// Modify source data
numbers.Add(6);

// NOW query executes - includes 6!
foreach (var n in query)
    Console.WriteLine(n);  // 3, 4, 5, 6

// Immediate execution (forces evaluation)
var list = numbers.Where(n => n > 2).ToList();     // ToList()
var count = numbers.Count(n => n > 2);              // Aggregation
var first = numbers.First(n => n > 2);              // Element access

Why it matters:

  • Query can be built incrementally
  • Source changes reflected automatically
  • Multiple enumeration = multiple executions (can cause performance issues)

12. What is the difference between Select and SelectMany?

Answer:

var departments = new[]
{
    new { Name = "IT", Employees = new[] { "Alice", "Bob" } },
    new { Name = "HR", Employees = new[] { "Charlie" } }
};

// Select: 1-to-1 mapping (returns nested collection)
var nested = departments.Select(d => d.Employees);
// Result: IEnumerable<string[]> = [["Alice","Bob"], ["Charlie"]]

// SelectMany: 1-to-many mapping (flattens)
var flat = departments.SelectMany(d => d.Employees);
// Result: IEnumerable<string> = ["Alice", "Bob", "Charlie"]

// SelectMany with result selector
var detailed = departments.SelectMany(
    d => d.Employees,
    (dept, emp) => new { Department = dept.Name, Employee = emp }
);
// Result: [{ IT, Alice }, { IT, Bob }, { HR, Charlie }]

Async/Await and Threading

13. What is the difference between async/await and parallel programming?

Answer:

// Async/Await: Non-blocking I/O, frees thread while waiting
public async Task<string> FetchDataAsync()
{
    // Thread returns to pool during await
    var data = await httpClient.GetStringAsync(url);
    return data;
}

// Parallel: Uses multiple threads for CPU-bound work
public void ProcessInParallel(List<int> numbers)
{
    Parallel.ForEach(numbers, n =>
    {
        // Each item processed on different thread
        DoHeavyComputation(n);
    });
}

// Task.Run: Offloads CPU work to thread pool
public async Task ProcessAsync()
{
    // Runs on thread pool thread
    var result = await Task.Run(() => HeavyComputation());
}
Aspect Async/Await Parallel
Purpose I/O-bound operations CPU-bound operations
Threads Frees thread during wait Uses multiple threads
Use case HTTP calls, DB queries Data processing, calculations

14. What happens if you don’t await an async method?

Answer:

// Fire and forget (dangerous!)
public void DoSomething()
{
    ProcessAsync();  // No await - method returns immediately
    Console.WriteLine("Continues without waiting");
}

private async Task ProcessAsync()
{
    await Task.Delay(1000);
    throw new Exception("This exception is lost!");  // Unobserved!
}

// Problems:
// 1. Exceptions are swallowed
// 2. No way to know when operation completes
// 3. Application might exit before completion

// Correct approaches:
await ProcessAsync();  // Wait for completion

// Or capture task for later
Task task = ProcessAsync();
// ... do other work ...
await task;  // Wait when needed

15. What is ConfigureAwait(false) and when should you use it?

Answer:

// By default, await captures and restores SynchronizationContext
public async Task UpdateUIAsync()
{
    var data = await FetchDataAsync();  // Captures UI context
    labelStatus.Text = data;  // Returns to UI thread βœ…
}

// ConfigureAwait(false) skips context capture
public async Task<string> FetchDataAsync()
{
    var response = await httpClient.GetAsync(url)
        .ConfigureAwait(false);  // Don't need original context

    // Continuation runs on any available thread
    return await response.Content.ReadAsStringAsync()
        .ConfigureAwait(false);
}

Use ConfigureAwait(false) when:

  • In library code (no UI context needed)
  • In ASP.NET Core (no sync context by default anyway)
  • For performance (avoids context switching overhead)

Don’t use when:

  • Continuing code needs UI thread access
  • In older ASP.NET (not Core) - can cause deadlocks if not careful

Memory Management

16. How does garbage collection work in .NET?

Answer:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     MANAGED HEAP                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   Generation 0 β”‚   Generation 1   β”‚     Generation 2        β”‚
β”‚   (Young)      β”‚   (Survivor)     β”‚     (Long-lived)        β”‚
β”‚                β”‚                  β”‚                         β”‚
β”‚  New objects   β”‚  Survived 1 GC   β”‚  Survived 2+ GCs        β”‚
β”‚  Collected     β”‚  Collected less  β”‚  Collected rarely       β”‚
β”‚  frequently    β”‚  frequently      β”‚  (Full GC)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

GC Process:

  1. Mark: Find all reachable objects from roots (stack, static fields)
  2. Sweep: Reclaim memory from unmarked objects
  3. Compact: Move surviving objects together, update references
// Force garbage collection (rarely needed)
GC.Collect();
GC.WaitForPendingFinalizers();

// Check generation
var obj = new object();
Console.WriteLine(GC.GetGeneration(obj));  // 0

// Large Object Heap (objects > 85KB)
var largeArray = new byte[100000];  // Goes to LOH
// LOH is only collected during Gen 2 collection

17. What is the IDisposable pattern and why is it important?

Answer:

public class ResourceHolder : IDisposable
{
    private FileStream _fileStream;
    private bool _disposed = false;

    public ResourceHolder(string path)
    {
        _fileStream = new FileStream(path, FileMode.Open);
    }

    // Public Dispose method
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);  // No need for finalizer
    }

    // Protected virtual for inheritance
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            // Dispose managed resources
            _fileStream?.Dispose();
        }

        // Free unmanaged resources here
        _disposed = true;
    }

    // Finalizer (only if you have unmanaged resources)
    ~ResourceHolder()
    {
        Dispose(false);
    }
}

// Usage with using statement
using (var holder = new ResourceHolder("file.txt"))
{
    // Use the resource
}  // Dispose called automatically

// C# 8+ using declaration
using var holder = new ResourceHolder("file.txt");
// Disposed at end of scope

.NET Core / .NET 5+

18. What is dependency injection and how does it work in .NET?

Answer:

// 1. Define services
public interface IEmailService
{
    Task SendAsync(string to, string message);
}

public class SmtpEmailService : IEmailService
{
    public async Task SendAsync(string to, string message)
    {
        // Send via SMTP
    }
}

// 2. Register in DI container
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IEmailService, SmtpEmailService>();
builder.Services.AddSingleton<IConfiguration>(config);
builder.Services.AddTransient<IValidator, OrderValidator>();

// 3. Inject via constructor
public class OrderController : ControllerBase
{
    private readonly IEmailService _emailService;

    public OrderController(IEmailService emailService)
    {
        _emailService = emailService;  // Injected automatically
    }
}

Service Lifetimes:

Lifetime Description Use Case
Transient New instance each request Stateless services
Scoped One instance per HTTP request DbContext, unit of work
Singleton One instance for app lifetime Configuration, caching

19. What is middleware in ASP.NET Core?

Answer:

// Middleware pipeline - request flows through each component
public void Configure(IApplicationBuilder app)
{
    // Order matters!
    app.UseExceptionHandler("/error");  // 1. Handle exceptions
    app.UseHttpsRedirection();           // 2. Redirect HTTP to HTTPS
    app.UseStaticFiles();                // 3. Serve static files
    app.UseRouting();                    // 4. Match routes
    app.UseAuthentication();             // 5. Authenticate user
    app.UseAuthorization();              // 6. Check permissions
    app.UseEndpoints(endpoints => { });  // 7. Execute endpoint
}

// Custom middleware
public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestTimingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();

        await _next(context);  // Call next middleware

        stopwatch.Stop();
        context.Response.Headers.Add("X-Response-Time",
            $"{stopwatch.ElapsedMilliseconds}ms");
    }
}

// Register: app.UseMiddleware<RequestTimingMiddleware>();

Entity Framework

20. What is the difference between Include and ThenInclude?

Answer:

// Include: Load direct navigation property
var orders = await context.Orders
    .Include(o => o.Customer)  // Load Customer for each Order
    .ToListAsync();

// ThenInclude: Load nested navigation properties
var orders = await context.Orders
    .Include(o => o.OrderItems)           // Load OrderItems
        .ThenInclude(oi => oi.Product)    // Load Product for each OrderItem
            .ThenInclude(p => p.Category) // Load Category for each Product
    .Include(o => o.Customer)             // Also load Customer
        .ThenInclude(c => c.Address)      // Load Address for Customer
    .ToListAsync();

// Explicit loading (after the fact)
var order = await context.Orders.FindAsync(1);
await context.Entry(order)
    .Collection(o => o.OrderItems)
    .LoadAsync();

21. How do you handle concurrency in Entity Framework?

Answer:

// Optimistic concurrency with RowVersion
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; }  // Concurrency token
}

// OnModelCreating alternative
modelBuilder.Entity<Product>()
    .Property(p => p.RowVersion)
    .IsRowVersion();

// Handling concurrency conflicts
try
{
    await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
    var entry = ex.Entries.Single();
    var databaseValues = await entry.GetDatabaseValuesAsync();
    var clientValues = entry.CurrentValues;

    // Strategy 1: Database wins
    entry.OriginalValues.SetValues(databaseValues);

    // Strategy 2: Client wins
    entry.OriginalValues.SetValues(databaseValues);
    // Keep current values, retry save

    // Strategy 3: Merge (custom logic)
    // Compare and merge values

    await context.SaveChangesAsync();
}

Design Patterns

22. Explain the Repository and Unit of Work patterns.

Answer:

// Repository: Abstracts data access
public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    void Update(T entity);
    void Delete(T entity);
}

// Unit of Work: Coordinates repositories, single transaction
public interface IUnitOfWork : IDisposable
{
    IRepository<Order> Orders { get; }
    IRepository<Product> Products { get; }
    Task<int> SaveChangesAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _context;

    public IRepository<Order> Orders { get; }
    public IRepository<Product> Products { get; }

    public UnitOfWork(AppDbContext context)
    {
        _context = context;
        Orders = new Repository<Order>(context);
        Products = new Repository<Product>(context);
    }

    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }

    public void Dispose() => _context.Dispose();
}

// Usage
public class OrderService
{
    private readonly IUnitOfWork _uow;

    public async Task CreateOrderAsync(Order order)
    {
        await _uow.Orders.AddAsync(order);

        var product = await _uow.Products.GetByIdAsync(order.ProductId);
        product.Stock -= order.Quantity;
        _uow.Products.Update(product);

        await _uow.SaveChangesAsync();  // Single transaction
    }
}

System Design

23. How would you design a rate limiter?

Answer:

// Token Bucket Algorithm implementation
public class TokenBucketRateLimiter
{
    private readonly int _maxTokens;
    private readonly double _refillRate;  // Tokens per second
    private double _tokens;
    private DateTime _lastRefill;
    private readonly object _lock = new();

    public TokenBucketRateLimiter(int maxTokens, double refillRate)
    {
        _maxTokens = maxTokens;
        _refillRate = refillRate;
        _tokens = maxTokens;
        _lastRefill = DateTime.UtcNow;
    }

    public bool TryAcquire()
    {
        lock (_lock)
        {
            RefillTokens();

            if (_tokens >= 1)
            {
                _tokens--;
                return true;
            }
            return false;
        }
    }

    private void RefillTokens()
    {
        var now = DateTime.UtcNow;
        var elapsed = (now - _lastRefill).TotalSeconds;
        var tokensToAdd = elapsed * _refillRate;

        _tokens = Math.Min(_maxTokens, _tokens + tokensToAdd);
        _lastRefill = now;
    }
}

// ASP.NET Core built-in rate limiting (.NET 7+)
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("fixed", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
    });

    options.AddSlidingWindowLimiter("sliding", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
        opt.SegmentsPerWindow = 4;
    });
});

24. How would you implement caching in a .NET application?

Answer:

// In-memory caching
public class CachedProductService : IProductService
{
    private readonly IMemoryCache _cache;
    private readonly IProductRepository _repository;

    public async Task<Product> GetByIdAsync(int id)
    {
        var cacheKey = $"product_{id}";

        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromMinutes(10);
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);

            return await _repository.GetByIdAsync(id);
        });
    }

    public void InvalidateProduct(int id)
    {
        _cache.Remove($"product_{id}");
    }
}

// Distributed caching with Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
    options.InstanceName = "MyApp_";
});

public class RedisProductService
{
    private readonly IDistributedCache _cache;

    public async Task<Product> GetByIdAsync(int id)
    {
        var cacheKey = $"product_{id}";
        var cached = await _cache.GetStringAsync(cacheKey);

        if (cached != null)
            return JsonSerializer.Deserialize<Product>(cached);

        var product = await _repository.GetByIdAsync(id);

        await _cache.SetStringAsync(cacheKey,
            JsonSerializer.Serialize(product),
            new DistributedCacheEntryOptions
            {
                SlidingExpiration = TimeSpan.FromMinutes(10)
            });

        return product;
    }
}

Quick Reference

Common Interview Topics Checklist

  • [ ] Value types vs Reference types
  • [ ] Boxing/Unboxing
  • [ ] Abstract class vs Interface
  • [ ] SOLID principles
  • [ ] IEnumerable vs IQueryable
  • [ ] Async/Await mechanics
  • [ ] Garbage collection
  • [ ] Dependency injection
  • [ ] Entity Framework (Include, tracking, concurrency)
  • [ ] Design patterns (Repository, Factory, Singleton)
  • [ ] Middleware pipeline
  • [ ] Exception handling best practices
  • [ ] Caching strategies
  • [ ] API versioning
  • [ ] Authentication/Authorization

Sources