.NET Interview Questions and Answers: Complete Guide for 2025

January 22, 2025.NET • C# • Interview • Software Development

.NET Core development and interview preparation

Loading...

Loading...

Preparing for a .NET interview requires a solid understanding of core concepts, advanced features, and real-world application patterns. This comprehensive guide covers essential .NET interview questions that you're likely to encounter, from fundamental concepts to advanced scenarios. Whether you're interviewing for a junior developer position or a senior architect role, this guide will help you demonstrate your expertise in .NET development.

Core .NET Concepts

What is .NET and How Does It Differ from .NET Framework?

.NET is a free, open-source, cross-platform framework for building modern applications. The key differences between .NET (formerly .NET Core) and .NET Framework include:

Understanding the Common Language Runtime (CLR)

The CLR is the execution engine of .NET that provides services like memory management, type safety, exception handling, and garbage collection. When you compile C# code, it's converted to Intermediate Language (IL), which the CLR then compiles to native machine code using Just-In-Time (JIT) compilation.

// Example: Understanding value types vs reference types
public class ReferenceTypeExample
{
    public int Value { get; set; }
}

public struct ValueTypeExample
{
    public int Value { get; set; }
}

// Value types are stored on the stack
ValueTypeExample vt = new ValueTypeExample { Value = 10 };

// Reference types are stored on the heap
ReferenceTypeExample rt = new ReferenceTypeExample { Value = 10 };

Garbage Collection in .NET

.NET uses a generational garbage collector with three generations (0, 1, 2). New objects start in Generation 0. Objects that survive a collection are promoted to the next generation. This design optimizes for the fact that most objects are short-lived.

Interview Question: "How does garbage collection work in .NET?"

Answer: The GC uses a mark-and-sweep algorithm. It identifies reachable objects starting from root references (static fields, local variables, CPU registers), marks them, and then sweeps unreachable objects. The GC can be triggered manually with GC.Collect(), but this is generally discouraged except in specific scenarios.

Advanced .NET Topics

Dependency Injection and IoC Containers

Dependency Injection (DI) is a design pattern that implements Inversion of Control (IoC) to achieve loose coupling. .NET provides built-in DI support through the Microsoft.Extensions.DependencyInjection package.

// Service registration
public void ConfigureServices(IServiceCollection services)
{
    // Transient: New instance every time
    services.AddTransient<IEmailService, EmailService>();
    
    // Scoped: One instance per HTTP request
    services.AddScoped<IUserRepository, UserRepository>();
    
    // Singleton: One instance for the application lifetime
    services.AddSingleton<ICacheService, CacheService>();
}

// Constructor injection
public class UserController
{
    private readonly IUserRepository _userRepository;
    
    public UserController(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }
}

Entity Framework Core: Code-First vs Database-First

Entity Framework Core supports multiple approaches to data modeling:

// Code-First example
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public List<Post> Posts { get; set; } = new();
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

// DbContext configuration
public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasMany(b => b.Posts)
            .WithOne(p => p.Blog)
            .HasForeignKey(p => p.BlogId);
    }
}

Coding Challenges and Examples

Implementing a Thread-Safe Singleton Pattern

A common interview question involves implementing a thread-safe singleton. Here's the recommended approach using Lazy<T>:

public class ThreadSafeSingleton
{
    private static readonly Lazy<ThreadSafeSingleton> _instance = 
        new Lazy<ThreadSafeSingleton>(() => new ThreadSafeSingleton());
    
    private ThreadSafeSingleton() { }
    
    public static ThreadSafeSingleton Instance => _instance.Value;
    
    public void DoWork()
    {
        // Singleton implementation
    }
}

// Usage
var singleton = ThreadSafeSingleton.Instance;
singleton.DoWork();

LINQ Performance Considerations

Understanding when to use LINQ methods and their performance implications is crucial:

// Deferred execution - query not executed until enumeration
var query = numbers.Where(n => n > 10).Select(n => n * 2);

// Immediate execution with ToList()
var results = numbers.Where(n => n > 10).Select(n => n * 2).ToList();

// Performance tip: Use FirstOrDefault() instead of Where().FirstOrDefault()
// Bad: O(n) - iterates entire collection
var bad = numbers.Where(n => n > 10).FirstOrDefault();

// Good: O(n) but stops at first match
var good = numbers.FirstOrDefault(n => n > 10);

Real-World Architectural Patterns

Repository Pattern Implementation

The Repository pattern abstracts data access logic and provides a cleaner separation of concerns:

public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
}

public class Repository<T> : IRepository<T> where T : class
{
    protected readonly DbContext _context;
    protected readonly DbSet<T> _dbSet;
    
    public Repository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }
    
    public virtual async Task<T> GetByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }
    
    public virtual async Task<IEnumerable<T>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();
    }
    
    public virtual async Task AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
        await _context.SaveChangesAsync();
    }
    
    public virtual async Task UpdateAsync(T entity)
    {
        _dbSet.Update(entity);
        await _context.SaveChangesAsync();
    }
    
    public virtual async Task DeleteAsync(int id)
    {
        var entity = await GetByIdAsync(id);
        if (entity != null)
        {
            _dbSet.Remove(entity);
            await _context.SaveChangesAsync();
        }
    }
}

Middleware Pipeline in ASP.NET Core

Understanding the middleware pipeline is essential for ASP.NET Core development:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Exception handling middleware (early in pipeline)
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }
    
    // HTTPS redirection
    app.UseHttpsRedirection();
    
    // Static files
    app.UseStaticFiles();
    
    // Routing
    app.UseRouting();
    
    // Authentication
    app.UseAuthentication();
    app.UseAuthorization();
    
    // Custom middleware
    app.Use(async (context, next) =>
    {
        // Pre-processing
        var startTime = DateTime.UtcNow;
        
        await next(); // Call next middleware
        
        // Post-processing
        var duration = DateTime.UtcNow - startTime;
        context.Response.Headers.Add("X-Response-Time", duration.TotalMilliseconds.ToString());
    });
    
    // Endpoints
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Common Interview Questions and Answers

Question 1: What is the difference between String and StringBuilder?

Answer: String is immutable—each operation creates a new string object, which can be inefficient for multiple concatenations. StringBuilder is mutable and uses an internal buffer, making it more efficient for string manipulation operations.

// Inefficient - creates multiple string objects
string result = "";
for (int i = 0; i < 1000; i++)
{
    result += i.ToString(); // Creates new string each time
}

// Efficient - uses internal buffer
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i.ToString());
}
string result = sb.ToString();

Question 2: Explain the difference between IQueryable and IEnumerable

Answer: IEnumerable executes queries in memory (client-side), while IQueryable builds expression trees that are translated to SQL and executed on the database server. Use IQueryable for database queries to leverage server-side filtering and reduce data transfer.

Question 3: What are Extension Methods?

Answer: Extension methods allow you to add methods to existing types without modifying their source code. They must be static methods in a static class and use the this keyword for the first parameter.

public static class StringExtensions
{
    public static bool IsValidEmail(this string email)
    {
        return !string.IsNullOrEmpty(email) && 
               email.Contains("@") && 
               email.Contains(".");
    }
}

// Usage
string email = "user@example.com";
bool isValid = email.IsValidEmail();

Behavioral Interview Tips for .NET Roles

Demonstrating Problem-Solving Skills

Questions to Ask the Interviewer

Mock Interview Practice Questions

Technical Scenario 1: Design a Caching Strategy

Question: "How would you implement a distributed caching solution for a high-traffic web application?"

Approach: Discuss using Redis or Azure Cache for Redis, implementing cache-aside pattern, setting appropriate TTL values, handling cache invalidation, and considering cache warming strategies for critical data.

Technical Scenario 2: Optimize Database Queries

Question: "A web application is experiencing slow page loads. How would you diagnose and fix the performance issues?"

Approach: Mention using Application Insights or profiling tools, analyzing slow queries, implementing proper indexing, using async/await for I/O operations, considering pagination, and implementing query result caching where appropriate.

Advanced .NET Topics

Dependency Injection and IoC Containers

Interview Question: "Explain dependency injection and how it's implemented in .NET."

Answer: Dependency Injection (DI) is a design pattern where dependencies are provided to a class rather than created internally. .NET Core includes a built-in DI container. Benefits include testability, loose coupling, and easier maintenance.

// Service registration
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IUserRepository, UserRepository>();
    services.AddSingleton<ICacheService, CacheService>();
    services.AddTransient<IEmailService, EmailService>();
}

// Constructor injection
public class UserService
{
    private readonly IUserRepository _repository;
    private readonly IEmailService _emailService;
    
    public UserService(IUserRepository repository, IEmailService emailService)
    {
        _repository = repository;
        _emailService = emailService;
    }
    
    public async Task CreateUserAsync(User user)
    {
        await _repository.AddAsync(user);
        await _emailService.SendWelcomeEmailAsync(user.Email);
    }
}

// Service lifetimes:
// - Singleton: One instance for the application lifetime
// - Scoped: One instance per request (web apps) or scope
// - Transient: New instance every time

Entity Framework Core Advanced Topics

Interview Question: "How do you optimize Entity Framework Core queries?"

Answer: Use AsNoTracking() for read-only queries, implement proper eager loading with Include(), use projections to select only needed columns, implement query splitting for complex queries, use compiled queries for repeated queries, and leverage raw SQL for performance-critical operations.

// AsNoTracking for read-only
var users = await _context.Users
    .AsNoTracking()
    .Where(u => u.IsActive)
    .ToListAsync();

// Eager loading
var orders = await _context.Orders
    .Include(o => o.Customer)
    .Include(o => o.Items)
        .ThenInclude(i => i.Product)
    .ToListAsync();

// Projection (select only needed fields)
var userNames = await _context.Users
    .Select(u => new { u.Id, u.Name, u.Email })
    .ToListAsync();

// Compiled query
private static readonly Func<AppDbContext, int, Task<User>> GetUserById =
    EF.CompileAsyncQuery((AppDbContext context, int id) =>
        context.Users.FirstOrDefault(u => u.Id == id));

// Usage
var user = await GetUserById(_context, userId);

// Raw SQL for complex queries
var results = await _context.Users
    .FromSqlRaw("SELECT * FROM Users WHERE Active = 1 AND CreatedDate > {0}", date)
    .ToListAsync();

Memory Management and Garbage Collection

Interview Question: "Explain .NET garbage collection and how to optimize memory usage."

Answer: .NET uses a generational garbage collector with three generations (0, 1, 2). Gen 0 is for short-lived objects, Gen 1 for medium-lived, and Gen 2 for long-lived objects. Optimize by reducing allocations, using object pooling, implementing IDisposable properly, and avoiding large object heap allocations.

// Object pooling example
public class ObjectPool<T> where T : class, new()
{
    private readonly ConcurrentQueue<T> _pool = new();
    private int _count = 0;
    private readonly int _maxSize;
    
    public ObjectPool(int maxSize = 100)
    {
        _maxSize = maxSize;
    }
    
    public T Rent()
    {
        if (_pool.TryDequeue(out var item))
        {
            return item;
        }
        
        if (_count < _maxSize)
        {
            Interlocked.Increment(ref _count);
            return new T();
        }
        
        return new T(); // Exceeded pool size
    }
    
    public void Return(T item)
    {
        if (_count <= _maxSize)
        {
            _pool.Enqueue(item);
        }
    }
}

// Proper IDisposable implementation
public class ResourceManager : IDisposable
{
    private bool _disposed = false;
    private readonly Stream _stream;
    
    public ResourceManager(string filePath)
    {
        _stream = File.OpenRead(filePath);
    }
    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _stream?.Dispose();
            }
            _disposed = true;
        }
    }
    
    ~ResourceManager()
    {
        Dispose(false);
    }
}

Design Patterns in .NET

Repository Pattern

public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
}

public class Repository<T> : IRepository<T> where T : class
{
    protected readonly DbContext _context;
    protected readonly DbSet<T> _dbSet;
    
    public Repository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }
    
    public virtual async Task<T> GetByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }
    
    public virtual async Task<IEnumerable<T>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();
    }
    
    public virtual async Task AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
        await _context.SaveChangesAsync();
    }
    
    public virtual async Task UpdateAsync(T entity)
    {
        _dbSet.Update(entity);
        await _context.SaveChangesAsync();
    }
    
    public virtual async Task DeleteAsync(int id)
    {
        var entity = await GetByIdAsync(id);
        if (entity != null)
        {
            _dbSet.Remove(entity);
            await _context.SaveChangesAsync();
        }
    }
}

Unit of Work Pattern

public interface IUnitOfWork : IDisposable
{
    IUserRepository Users { get; }
    IOrderRepository Orders { get; }
    Task<int> SaveChangesAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;
    private IUserRepository _users;
    private IOrderRepository _orders;
    
    public UnitOfWork(DbContext context)
    {
        _context = context;
    }
    
    public IUserRepository Users =>
        _users ??= new UserRepository(_context);
    
    public IOrderRepository Orders =>
        _orders ??= new OrderRepository(_context);
    
    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }
    
    public void Dispose()
    {
        _context?.Dispose();
    }
}

Performance Optimization Techniques

String Handling

// BAD: String concatenation in loop
string result = "";
foreach (var item in items)
{
    result += item; // Creates new string each time
}

// GOOD: StringBuilder
var sb = new StringBuilder();
foreach (var item in items)
{
    sb.Append(item);
}
string result = sb.ToString();

// BETTER: String.Join
string result = string.Join("", items);

// BEST: Span<char> for .NET Core
Span<char> buffer = stackalloc char[100];
// Process with Span

Caching Strategies

// Memory cache
public class CacheService
{
    private readonly IMemoryCache _cache;
    
    public CacheService(IMemoryCache cache)
    {
        _cache = cache;
    }
    
    public async Task<T> GetOrSetAsync<T>(
        string key, 
        Func<Task<T>> factory, 
        TimeSpan? expiration = null)
    {
        if (_cache.TryGetValue(key, out T cachedValue))
        {
            return cachedValue;
        }
        
        var value = await factory();
        var options = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromMinutes(5),
            SlidingExpiration = TimeSpan.FromMinutes(2)
        };
        
        _cache.Set(key, value, options);
        return value;
    }
}

// Distributed cache (Redis)
public class DistributedCacheService
{
    private readonly IDistributedCache _cache;
    
    public async Task<T> GetOrSetAsync<T>(
        string key, 
        Func<Task<T>> factory, 
        DistributedCacheEntryOptions options = null)
    {
        var cached = await _cache.GetStringAsync(key);
        if (cached != null)
        {
            return JsonSerializer.Deserialize<T>(cached);
        }
        
        var value = await factory();
        var serialized = JsonSerializer.Serialize(value);
        
        await _cache.SetStringAsync(
            key, 
            serialized, 
            options ?? new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
            });
        
        return value;
    }
}

Security Best Practices

Authentication and Authorization

// JWT Authentication
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = Configuration["Jwt:Issuer"],
                ValidAudience = Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
            };
        });
}

// Authorization policies
services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => 
        policy.RequireRole("Admin"));
    
    options.AddPolicy("Over18", policy => 
        policy.RequireClaim("Age", "18", "19", "20")); // etc.
});

// Usage in controllers
[Authorize(Policy = "AdminOnly")]
public class AdminController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAdminData()
    {
        return Ok("Admin data");
    }
}

FAQs: .NET Interview Questions

Q: What's the difference between .NET Framework and .NET Core/.NET 5+?

A: .NET Framework is Windows-only, while .NET Core/.NET 5+ is cross-platform. .NET Core has better performance, is open-source, has a modular architecture, and supports side-by-side installation. .NET 5+ is the unified platform replacing both.

Q: Explain the difference between IEnumerable, ICollection, and IList.

A: IEnumerable provides basic iteration. ICollection adds Count and modification methods (Add, Remove, Clear). IList adds index-based access (this[int index]). Use the most specific interface that meets your needs.

Q: What is the difference between ref and out parameters?

A: Both pass parameters by reference. ref requires the variable to be initialized before passing. out doesn't require initialization, but the method must assign a value. Use out when you need to return multiple values.

Q: How does .NET handle memory management?

A: .NET uses automatic garbage collection. Objects are allocated on the managed heap. The GC automatically reclaims memory for objects that are no longer referenced. There are three generations (0, 1, 2) based on object lifetime.

Q: What is the difference between String and StringBuilder?

A: String is immutable - each operation creates a new string. StringBuilder is mutable and more efficient for multiple string operations. Use StringBuilder when concatenating strings in loops or performing many modifications.

Conclusion

Mastering .NET interview questions requires a combination of theoretical knowledge and practical experience. Focus on understanding core concepts deeply, practicing coding challenges, and being able to explain your thought process. Remember that interviewers value candidates who can not only write code but also understand the "why" behind design decisions and can communicate technical concepts clearly.

Key Areas to Master: C# language features, .NET Core architecture, async/await patterns, Entity Framework Core, dependency injection, design patterns, performance optimization, security best practices, and testing strategies. Practice implementing real-world scenarios, contribute to open-source .NET projects, and stay updated with the latest .NET features and best practices.

Continue practicing with real-world scenarios, contribute to open-source .NET projects, and stay updated with the latest .NET features and best practices. Good luck with your interview preparation!