C# Interview Questions and Answers

These are the C# questions you'll encounter in technical interviews, each with a detailed written answer. Master core OOP concepts, then progress through async/await, LINQ, and the .NET ecosystem.

40 questions with answersLast updated: Oct 10, 2026
Start a mock interview

What is C#?

C# is a modern, object-oriented language developed by Microsoft. It runs on the .NET Framework and is used for web development, game development (Unity), desktop applications, and backend systems.

C# interviews focus on object-oriented design, async/await, LINQ, delegates, and the .NET ecosystem. You'll discuss memory management, garbage collection, and how to write clean, maintainable code.

C# proficiency is essential for .NET developers and critical for Unity game development. Master the language and you'll be competitive for a wide range of roles.

Reading answers is not the same as giving them. Sit a full mock interview and get scored on how you actually explain them.

Try a mock interview

C# Interview Questions and Answers

1Multiple choice

How would you describe C# and what environments does it target?

Answer

C# is a multi-paradigm language running on the .NET runtime, targeting Windows desktop (WinForms, WPF), web (ASP.NET Core), mobile (Xamarin), and cloud applications.

2Multiple choice

Explain the Common Language Runtime and its role in executing C# programs

Answer

The CLR compiles C# to Intermediate Language (IL), then JIT compiles to native code at runtime, enabling cross-platform execution and language interoperability.

3Multiple choice

What is the .NET Framework and how does it support C# development?

Answer

.NET (and .NET Core/.NET 5+) provides the runtime, comprehensive standard library, and development tools for diverse application types.

4Multiple choice

How does LINQ enable querying data sources in C#?

Answer

LINQ (Language Integrated Query) offers query methods (Where, Select, GroupBy) and SQL-like syntax that work with any data source via deferred execution.

5Multiple choice

Explain how properties provide controlled access to class members in C#

Answer

Properties use get and set accessors (can be private) to control field access, enabling validation, computed values, and notifications on changes.

6Multiple choice

How do async and await keywords simplify asynchronous programming?

Answer

Async/await is syntactic sugar over Tasks that makes asynchronous code read like synchronous code while maintaining non-blocking execution.

7Multiple choice

What are delegates and how do they enable callback mechanisms in C#?

Answer

Delegates define the signature of a method. Events build on delegates to enable the observer pattern, allowing multiple subscribers to handle notifications.

8Multiple choice

Explain lambda expressions and their role in functional programming in C#

Answer

Lambda expressions (x => x * 2) create inline functions, essential for LINQ queries and functional patterns. They capture local variables in closures.

9Multiple choice

How does dependency injection improve code testability and flexibility?

Answer

DI inverts control: classes depend on abstractions, not concrete implementations. DI containers manage object creation, enabling easy testing and configuration.

10Multiple choice

How does automatic garbage collection in C# manage memory differently from manual approaches?

Answer

GC automatically frees unreachable objects but doesn't manage unmanaged resources (file handles, database connections). IDisposable and using statements handle those.

11Written answer

Describe C#'s design goals and its relationship to the .NET ecosystem

Answer

C# was designed as a modern, simple, type-safe language for the .NET platform. It combines the power of C++ with the simplicity of Java, borrowing best practices from both. C# runs on the CLR, which provides memory management, garbage collection, and runtime type safety. The language supports multiple paradigms: object-oriented (classes, inheritance, polymorphism), functional (lambdas, LINQ), and procedural. C# evolves with each .NET release, adding features like nullable reference types, records, and pattern matching. The .NET ecosystem provides comprehensive libraries for web, desktop, mobile, and cloud development.

12Written answer

Explain how the CLR enables language interoperability and manages program execution

Answer

The CLR is a virtual machine that compiles C# to Intermediate Language (IL), a platform-independent bytecode. At runtime, JIT (Just-In-Time) compilation converts IL to native machine code. Multiple languages (C#, VB.NET, F#) compile to IL, enabling interoperability: a C# class can inherit from a VB.NET class. CLR manages execution: memory allocation on the heap, garbage collection to reclaim unused objects, exception handling, and type safety through verification. This abstraction enables the same IL to run on Windows, Linux, and macOS without recompilation.

13Written answer

Describe the components of .NET and how .NET Core differs from the full Framework

Answer

.NET provides CLR, Base Class Library (BCL) with collections, I/O, and reflection, and language compilers. The .NET Framework (Windows-only) includes additional libraries for WinForms, WPF, and ASP.NET. .NET Core (now .NET 5+) is cross-platform (Windows, Linux, macOS), open-source, and modular, allowing inclusion only of needed libraries. .NET Core enables containerization and cloud deployment. Modern development uses .NET 5+ for new projects; .NET Framework is in maintenance mode. ASP.NET Core (web framework) and Entity Framework Core (ORM) run on both platforms.

14Written answer

Explain LINQ's deferred execution model and how it works with different data sources

Answer

LINQ uses deferred execution: queries don't run immediately when constructed, only when enumerated (in a foreach or ToList()). This enables composing queries: Where() filters, Select() transforms, both returning IEnumerable. The query is executed once during enumeration. Different providers handle different sources: LINQ-to-Objects for collections, LINQ-to-SQL for databases, LINQ-to-XML for XML. Each provider translates LINQ methods to the appropriate query format (SQL for databases). Deferred execution allows combining filters efficiently; executing too early causes unnecessary iteration.

15Written answer

Explain how properties enable encapsulation while providing convenient access syntax

Answer

Properties use get/set accessors (can be separate access levels: public get, private set). Behind the scenes, they typically wrap a private field. This enables validation in the setter, notifications via PropertyChanged event, and computed values in the getter. Example: public string Email { get; set; } is automatic, storing in a compiler-generated field. The syntax feels like field access but provides method-like control. Properties can be virtual for overriding or abstract in interfaces. This design provides data hiding while maintaining clean syntax.

16Written answer

Describe how async/await works with Tasks to enable responsive asynchronous code

Answer

Async methods return Task or Task<T>. Await pauses the method, returning control to the caller. When the awaited operation completes, execution resumes. This avoids blocking threads: a web server can handle many requests without creating threads for each. Async methods are compiled to state machines by the compiler. Tasks can wrap I/O operations (file, network) that don't require CPU. Combining async I/O with thread pools enables high concurrency without thread explosion. Exception handling works as expected: try-catch around awaits catches asynchronous exceptions.

17Written answer

Explain delegates and events, and how they enable observer patterns

Answer

A delegate is a type-safe function pointer: public delegate void OnDataReceived(string data); declares a delegate type. A class can define an event: public event OnDataReceived DataReceived; Subscribers register: obj.DataReceived += handler; and unregister: obj.DataReceived -= handler; The owner publishes: DataReceived?.Invoke(data); This decouples publishers from subscribers. Multiple handlers can subscribe without knowing about each other. Events prevent subscribers from clearing others' handlers or invoking the event (public field would allow this). This pattern is fundamental to UI frameworks and observer implementations.

18Written answer

Describe lambda syntax and how they integrate with LINQ and functional patterns

Answer

Lambda syntax is (parameters) => expression or (parameters) => { statements }. Examples: x => x * 2 or (x, y) => x + y. Lambdas capture local variables, creating closures. They integrate seamlessly with LINQ: list.Where(x => x > 10).Select(x => x.ToString()). Lambdas reduce boilerplate compared to anonymous methods or named functions. They enable functional patterns: Function<int, int> square = x => x * x. The compiler infers types from context. Lambdas can have side effects, though pure functions (no side effects) are preferred in LINQ for predictable execution.

19Written answer

Explain how DI containers manage object lifecycles and dependencies

Answer

DI containers (e.g., Microsoft.Extensions.DependencyInjection) maintain a registry: IServiceCollection.AddScoped<IRepository, Repository>(); Scoped means one instance per request; Singleton means one globally; Transient means new each time. At runtime, requesting IRepository returns a Repository instance. The container recursively resolves dependencies: if Repository needs IDbContext, the container creates that first. Configuration in Startup or Program: services.AddScoped<IUserService>(sp => new UserService(sp.GetRequiredService<IRepository>())). Constructor injection receives dependencies. This decouples classes from instantiation, enabling testing (inject mocks) and runtime substitution without code changes.

20Written answer

Describe generational garbage collection and the finalization/disposal pattern

Answer

GC organizes heap into generations: Gen0 (new objects), Gen1 (survived one GC), Gen2 (old). GC collects Gen0 frequently, older generations rarely. This optimizes for objects dying young. Finalizers (~ClassName()) run before GC reclaims objects but add overhead. The IDisposable pattern is preferred: public void Dispose() { /* clean up */ } Using statements: using (var file = new StreamReader(path)) { } call Dispose automatically. For unmanaged resources (file handles, network sockets), implement IDisposable and use using statements. Modern using statements (C# 8+) simplify this. Finalizers are the safety net if Dispose wasn't called.

21Spoken answer

Walk through how you would design a REST API using ASP.NET Core with proper dependency injection

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I created controllers inheriting from ControllerBase, each with actions returning IActionResult. I used dependency injection to inject services: private readonly IUserService _service; in the constructor. Services used interfaces for decoupling. I configured middleware: app.UseRouting() and app.UseEndpoints(). I implemented proper error handling, returning appropriate HTTP status codes. I used async actions: public async Task<IActionResult> GetUser(int id). Validation occurred in services, not controllers. I structured as Presentation → Service → Repository → Database, with interfaces between layers.

22Spoken answer

Describe a situation where you used async/await to improve application responsiveness

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I had an API that made sequential calls to multiple external services, blocking the UI thread. Converting to async with Task.WhenAll() ran them in parallel. Each service method was async Task<T>. The controller action was async. This freed the UI thread, allowing other requests to be processed. Response time improved significantly without adding threads. I handled timeouts with CancellationToken and proper exception handling.

23Spoken answer

Explain how you would implement the observer pattern using delegates and events

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I created a delegate: public delegate void OnOrderStatusChanged(Order order); and an event in the Order class. UI and notification services subscribed: order.StatusChanged += (o) => UpdateUI(o); When order status changed, I invoked the event: StatusChanged?.Invoke(this); Multiple handlers executed independently. Unsubscribing prevented memory leaks. This pattern decoupled order processing from its side effects.

24Spoken answer

Talk about a time you optimized LINQ queries by understanding deferred execution

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I was executing a LINQ query in a loop, running the query thousands of times. Calling ToList() once before the loop cached results, reducing database hits from thousands to one. I also avoided Select(x => x.Property) in the query, letting the database handle filtering before retrieving data. Understanding that LINQ-to-SQL translates to SQL taught me to defer execution and push filters to the database.

25Spoken answer

Describe how you would structure a large application using dependency injection and interfaces

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I structured the application: Domain (business logic), Application (use cases), Infrastructure (database, external services), Presentation (UI). Interfaces defined contracts between layers. Dependency injection injected implementations. The IoC container managed lifetimes. Adding new implementations didn't require changing existing code. Testing was easy: inject mocks of dependencies. This architecture scaled as the application grew.

26Spoken answer

Explain your approach to handling exceptions in asynchronous operations

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I wrapped await calls in try-catch blocks. For multiple concurrent operations, I used Task.WhenAll() inside try-catch to catch any task's exception. I used TaskCompletionSource to convert callbacks to Tasks for consistent error handling. I ensured operations could be cancelled with CancellationToken. I logged exceptions before rethrowing or returning error responses.

27Spoken answer

Walk through how you would implement a custom LINQ provider for a specific data source

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I created an IQueryable provider implementing IQueryable<T> and IQueryProvider. The visitor pattern walked the expression tree, translating it to my data source's query format. I implemented methods like Where, Select, and GroupBy as extension methods. This allowed LINQ syntax to work with custom data sources without requiring the LINQ provider library.

28Spoken answer

Describe how you would use generics to create a reusable repository pattern

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I defined IRepository<T> with methods like GetAsync, AddAsync, UpdateAsync, SaveAsync. Repository<T> implemented this, using Entity Framework Core internally. Controllers depended on IRepository, not the concrete class. Adding new entity types required minimal code: inherit Repository<Entity> or configure mapping. This pattern made testing easy: I created a mock repository returning test data.

29Spoken answer

Talk about migrating legacy code to use properties instead of public fields

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

Legacy code had public int Age; { get; set; }. I refactored to private int _age; public int Age { get { return _age; } set { _age = value; } }. This allowed adding validation later without changing callers. I gradually moved more logic into properties as needed. Properties are syntactic sugar for methods, maintaining backward compatibility while enabling encapsulation.

30Spoken answer

Explain how you would implement a plugin system using reflection and dependency injection

This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.

Answer

I used reflection: Assembly.GetExecutingAssembly().GetTypes() found all types. I filtered for implementations of IPlugin. For each, I registered with DI: services.AddScoped(pluginType). At runtime, requesting IEnumerable<IPlugin> gave all plugins. Each plugin was discovered and loaded without hardcoding. This enabled adding plugins by dropping assemblies in a folder.

31Coding

Write an async method that fetches data from multiple APIs concurrently and combines results

public async Task<CombinedData> FetchDataAsync(string id) {
    // Fetch from multiple APIs concurrently
}

Answer

public async Task<CombinedData> FetchDataAsync(string id) {
    var task1 = FetchFromApiOneAsync(id);
    var task2 = FetchFromApiTwoAsync(id);
    var task3 = FetchFromApiThreeAsync(id);
    await Task.WhenAll(task1, task2, task3);
    return new CombinedData { Data1 = await task1, Data2 = await task2, Data3 = await task3 };
}
32Coding

Use LINQ to filter and transform a collection of products based on multiple criteria

public List<ProductSummary> GetDiscountedProducts(List<Product> products, decimal minDiscount) {
    // Use LINQ to filter and transform
}

Answer

public List<ProductSummary> GetDiscountedProducts(List<Product> products, decimal minDiscount) {
    return products
        .Where(p => p.Discount >= minDiscount)
        .Where(p => p.Stock > 0)
        .OrderByDescending(p => p.Discount)
        .Select(p => new ProductSummary { Name = p.Name, DiscountedPrice = p.Price * (1 - p.Discount) })
        .ToList();
}
33Coding

Implement a generic repository class that works with any entity type

public class Repository<T> : IRepository<T> where T : class {
    private readonly DbContext _context;
    // Implement CRUD operations
}

Answer

public class Repository<T> : IRepository<T> where T : class {
    private readonly DbContext _context;
    public Repository(DbContext context) { _context = context; }
    public async Task<T> GetByIdAsync(int id) { return await _context.Set<T>().FindAsync(id); }
    public async Task AddAsync(T entity) { await _context.Set<T>().AddAsync(entity); }
    public void Remove(T entity) { _context.Set<T>().Remove(entity); }
    public async Task SaveAsync() { await _context.SaveChangesAsync(); }
}
34Coding

Create a class that publishes events when state changes occur

public delegate void OnStateChanged(StateChangedEventArgs args);

public class StateManager {
    public event OnStateChanged StateChanged;
    // Implement state change notification
}

Answer

public delegate void OnStateChanged(StateChangedEventArgs args);

public class StateManager {
    public event OnStateChanged StateChanged;
    private string _state;
    public string State {
        get { return _state; }
        set {
            if (_state != value) {
                _state = value;
                StateChanged?.Invoke(new StateChangedEventArgs { NewState = value });
            }
        }
    }
}
35Coding

Implement a property with validation logic that throws exceptions for invalid values

public class User {
    private string _email;
    public string Email {
        get { return _email; }
        set {
            // Add validation
        }
    }
}

Answer

public class User {
    private string _email;
    public string Email {
        get { return _email; }
        set {
            if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Email cannot be empty");
            if (!value.Contains("@")) throw new ArgumentException("Invalid email format");
            _email = value;
        }
    }
}
36Coding

Sort a list of employees by multiple criteria using lambda expressions

public List<Employee> SortEmployees(List<Employee> employees) {
    // Use OrderBy with lambda expressions
}

Answer

public List<Employee> SortEmployees(List<Employee> employees) {
    return employees
        .OrderBy(e => e.Department)
        .ThenByDescending(e => e.Salary)
        .ThenBy(e => e.Name)
        .ToList();
}
37Coding

Create a simple LINQ query that demonstrates method chaining

public List<string> ProcessOrders(List<Order> orders) {
    // Use LINQ methods: Where, Select, GroupBy, OrderBy
}

Answer

public List<string> ProcessOrders(List<Order> orders) {
    return orders
        .Where(o => o.Total > 100)
        .GroupBy(o => o.Customer)
        .Select(g => $"{g.Key}: {g.Count()} orders, Total: ${g.Sum(o => o.Total)}")
        .OrderByDescending(s => s)
        .ToList();
}
38Coding

Implement a method that executes multiple async operations in parallel and waits for all

public async Task<T[]> ExecuteTasksInParallel<T>(params Task<T>[] tasks) {
    // Execute tasks in parallel
}

Answer

public async Task<T[]> ExecuteTasksInParallel<T>(params Task<T>[] tasks) {
    return await Task.WhenAll(tasks);
}
39Coding

Implement IDisposable correctly to manage unmanaged resources

public class DatabaseConnection : IDisposable {
    private IntPtr _handle;
    // Implement Dispose pattern
}

Answer

public class DatabaseConnection : IDisposable {
    private IntPtr _handle;
    private bool _disposed = false;
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing) {
        if (!_disposed) {
            if (disposing) { /* managed cleanup */ }
            if (_handle != IntPtr.Zero) { CloseHandle(_handle); }
            _disposed = true;
        }
    }
    ~DatabaseConnection() { Dispose(false); }
}
40Coding

Use a dictionary to cache results and avoid redundant processing

public class DataCache {
    private Dictionary<string, CachedData> _cache;
    // Implement caching with dictionary
}

Answer

public class DataCache {
    private Dictionary<string, CachedData> _cache = new();
    public CachedData GetOrCompute(string key, Func<CachedData> compute) {
        if (!_cache.TryGetValue(key, out var cached)) {
            cached = compute();
            _cache[key] = cached;
        }
        return cached;
    }
}