47 min read

70 C# Interview Questions That Will Make You Interview-Ready in 2026

Article Summary

Preparing for C# interview questions means reviewing a curated set of technical topics employers commonly test. This article covers 70 questions spanning OOP, inheritance, LINQ, generics, and exception handling. You'll walk into your next C# developer interview ready to answer with confidence.

Technical interviews can feel intimidating, but solid preparation makes all the difference. Whether you’re a fresh graduate or a seasoned developer, walking into an interview with confidence comes from knowing your fundamentals inside and out.

This guide provides over 70 C# programming questions organized by experience level — from beginner to advanced. Each question includes clear explanations and code examples where relevant. You might also want to practice with some C# projects to reinforce these concepts.

These essential C# questions and answers will help you understand the technical concepts of the language. You’ll walk into a meeting with the hiring manager with confidence. As a developer myself, I use these concepts daily.

What is C#?

C# is a modern, statically typed, object-oriented programming language developed by Microsoft. It is primarily used for building web applications, desktop applications, cloud services, games, mobile applications, and many other types of software.

C# interview questions for beginners

Before we get into the beginner questions, I’d recommend my Udemy course, C#/.NET – 50 Essential Interview Questions (Junior Level). It’s built to help you understand C#/.NET, not just memorize answers for an interview.

1. What is a class?

A class is a blueprint for creating objects. It defines what data an object stores (fields and properties) and what behavior it provides (methods). A single class can be used to create many objects, each with its own state.

Below is an example of a class:

public class Employee
{
public string FirstName { get; set; }

public string LastName { get; set; }

public string FullName()
{
return $"{FirstName} {LastName}";
}
}

Each Employee object has its own values for FirstName and LastName, while all Employee objects share the same structure and behavior defined by the class.

2. What are the main concepts of object-oriented programming?

Object-oriented programming (OOP) organizes code around objects that combine data and behavior, whereas procedural programming organizes code around functions that operate on data. The key concepts of OOP are: encapsulation, abstraction, polymorphism, and inheritance.

3. What is an object?

An object is an instance of a class. While a class serves as a blueprint, an object is a concrete entity created from that blueprint. Each object has its own data (state) and can use the methods defined by its class. In C#, you typically create an object using the new keyword.

For example, if Person is a class, then john and jane are two different objects (instances) of that class. Both objects have the same set of properties and methods, but they can store different data, such as different names and ages.

See the syntax for defining a class and instantiating an object below.

//an instance of the Employee class
Employee employee = new("John", "Grande");

Console.WriteLine(employee.FullName);

public class Employee
{
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}

public string FirstName { get; }

public string LastName { get; }

public string FullName => $"{FirstName} {LastName}";
}

4. What is a constructor, and what are its different types?

A constructor is a special class member that is executed when a new object is created. Constructors are commonly used to initialize an object’s state by assigning values to its fields or properties.

If you don’t define any constructors, the C# compiler automatically provides a parameterless constructor. However, once you define your own constructor, the compiler no longer generates one automatically.

Common constructor types in C# include parameterless constructors, parameterized constructors, static constructors, and private constructors.

Below are examples of different constructor types.

public class Student
{
private string _firstName;
private string _lastName;

//parameterless constructor
public Student()
{
//code goes here
}

//parameterized constructor
public Student(string firstName, string lastName)
{
_firstName = firstName;
_lastName = lastName;
}

//static constructor
static Student()
{
//code goes here
}

//copy constructor
public Student(Student student)
{
_firstName = student._firstName;
_lastName = student._lastName;
}
}

5. What is a destructor in C#?

A destructor (also called a finalizer) is a special method that the .NET runtime may execute before an object is garbage collected. It is used to clean up unmanaged resources if necessary. Developers do not call destructors directly, and they are rarely needed because memory management is handled automatically by the Garbage Collector.

public class Purchase
{
    //Syntax to write a destructor.
    ~Purchase()
    {
        //code here to release resources.
    }
}

6. Is C# code managed or unmanaged code?

Managed code is code whose execution is managed by the Common Language Runtime (CLR). C# applications are managed, whereas traditional C++ applications are typically unmanaged. 

C# code is considered managed code because it runs under the supervision of the Common Language Runtime (CLR). The C# compiler first compiles source code into Intermediate Language (IL), which is then executed by the CLR.

The CLR provides services such as automatic memory management through garbage collection, type safety, exception handling, and security. Because of this, developers do not need to manually free managed memory, reducing the risk of common errors such as memory leaks.

By contrast, traditional C++ applications are typically unmanaged code, meaning they execute directly as native machine code without being managed by the CLR.

7. What is the Common Language Runtime (CLR)?

The Common Language Runtime (CLR) is the execution environment for .NET applications. It manages code execution and provides services such as automatic memory management, garbage collection, exception handling, type safety, and security. When you compile C# code, it is first converted into Intermediate Language (IL). At runtime, the CLR typically uses a Just-In-Time (JIT) compiler to translate IL into native machine code before execution.

8. What is garbage collection in C#?

Garbage collection is automatic memory management that reclaims memory occupied by objects that are no longer reachable. The garbage collector in the CLR periodically identifies objects that are no longer referenced by application code and reclaims their memory. This significantly reduces the risk of memory management errors, such as forgetting to free allocated memory or using dangling pointers, although memory leaks can still occur if objects remain unintentionally referenced.

9. What are value types and reference types?

C# types are divided into value types and reference types. Value types store their data directly, while reference type variables store a reference to an object located elsewhere in memory. As a result, assigning a value type copies the value itself, whereas assigning a reference type copies the reference to the same object.

Examples of value types include built-in types such as bool, byte, char, int, and decimal, as well as all structs. Examples of reference types include string, arrays, or Dictionaries, as well as all classes, interfaces, and delegates.

10. What is a namespace?

A namespace is used to organize related types, such as classes, interfaces, structs, enums, and delegates. It helps avoid naming conflicts by allowing types with the same name to exist in different namespaces. Although types can be declared in the global namespace, organizing them into namespaces is considered best practice.

Refer to the syntax below.

namespace Company.Project.Models
{
public class Person
{
}
}

11. What is encapsulation?

Encapsulation is the practice of hiding an object’s internal state and controlling how it can be accessed or modified. Instead of allowing direct access to data, a class exposes a public interface, such as properties and methods, that enforce validation and business rules. This helps protect the object’s state and makes the code easier to maintain.

Below is an example of encapsulation.

public class BankAccount
{
public decimal Balance { get; private set; }

public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount));

Balance += amount;
}
}

12. What is abstraction?

Abstraction is the practice of exposing only the functionality that users of a class need while hiding the implementation details. It allows developers to interact with an object through a simple, well-defined interface without needing to understand how the object works internally.

Consider the DateTime type in C#. Developers interact with it through a simple API, using properties such as Year or Day and methods like AddDays() or AddMonths(). They do not need to know how a date and time are represented internally. The implementation details are hidden behind the public interface.

Internally, DateTime represents a point in time as the number of 100-nanosecond intervals (ticks) since January 1, year 1. However, developers rarely need to know this because the type exposes a much simpler and more intuitive API. This is abstraction: users focus on what they want to do, while the implementation details remain hidden.

13. What is polymorphism?

Polymorphism is the ability to treat objects of different types through a common interface or base class. Although the calling code invokes the same method, each object can provide its own implementation, resulting in different behavior at runtime.

For example, suppose an application defines an IDataAccess interface with a Save() method. Both SqlDataAccess and SpreadsheetDataAccess implement this interface, each providing its own implementation of Save(). Code can interact with an IDataAccess object without knowing its concrete type. At runtime, the appropriate Save() implementation is executed based on the actual object being used.

IDataAccess dataAccess = new SqlDataAccess();
// or:
// IDataAccess dataAccess = new SpreadsheetDataAccess();

dataAccess.Save(customer);

public interface IDataAccess
{
void Save(Customer customer);
}

public class SqlDataAccess : IDataAccess
{
public void Save(Customer customer)
{
Console.WriteLine("Saving to SQL database...");
}
}

public class SpreadsheetDataAccess : IDataAccess
{
public void Save(Customer customer)
{
Console.WriteLine("Saving to spreadsheet...");
}
}

14. What is an interface?

An interface is a type that defines a contract for other types. It specifies the members that a class or struct must provide, without specifying how those members should be implemented. Any class or struct that implements the interface must provide implementations for all required members.

For example:

public interface ILogger
{
void Log(string message);
}

public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}

public class FileLogger : ILogger
{
public void Log(string message)
{
File.AppendAllText("log.txt", message + Environment.NewLine);
}
}

15. What is inheritance?

Inheritance is a mechanism that allows one class to derive from another class. The derived class automatically inherits the accessible fields, properties, and methods of the base class and can add new members or override virtual methods to provide specialized behavior.

Refer to the below example:

public class Person
{
public string Name { get; set; }

public virtual void Introduce()
{
Console.WriteLine($"Hi, I'm {Name}.");
}
}

public class Employee : Person
{
public string JobTitle { get; set; }

public override void Introduce()
{
Console.WriteLine($"Hi, I'm {Name} and I work as a {JobTitle}.");
}
}

16. Does C# support multiple inheritance?

No. C# does not support multiple inheritance of classes. A class can inherit from only one base class, although it can implement any number of interfaces.

One reason for this design is to avoid the diamond problem.

A

/ \

B C

\ /

D

Imagine class D inherits from both classes B and C, and both B and C inherit from class A. If both B and C override the same method from A, it becomes ambiguous which implementation D should inherit. By allowing only a single base class, C# eliminates this ambiguity.

When a class needs functionality from multiple sources, C# encourages using interfaces to define contracts and composition to reuse implementations instead of relying on multiple inheritance.

17. What are the differences between ref and out keywords?

Both ref and out are used to pass arguments by reference, but they have key differences. The ref keyword requires the variable to be initialized before passing it to the method. The out keyword does not require initialization, but the method must assign a value before returning.

Use ref when you want to read and modify an existing value. Use out when the method needs to return multiple values.

public class RefOutExample
{
 // ref example - variable must be initialized
 public void AddTen(ref int number)
 {
 number += 10;
 }

 // out example - method must assign value
 public void GetValues(out int x, out int y)
 {
 x = 10;
 y = 20;
 }

 public void Demo()
 {
 int refValue = 5;
 AddTen(ref refValue); // refValue is now 15

 int outX, outY;
 GetValues(out outX, out outY); // outX = 10, outY = 20
 }
}

18. What is the difference between == and .Equals() in C#?

The == operator and the Equals() method are both used to compare objects, but they are not always equivalent. By default, == compares references for reference types, while Equals() compares values. However, many .NET types, such as String, overload the == operator and override Equals() so that both compare the object’s contents instead of its reference. Value types typically compare their values with both == (if supported) and Equals().

The exact behavior depends on the type being compared, because both the == operator and the Equals() method can be customized by the type’s implementation.

Person p1 = new Person("John");
Person p2 = new Person("John");

p1 == p2 // false
p1.Equals(p2) // false

Intermediate C# interview questions

Already past the basics? I also put together a course for mid-level professionals: C#/.NET – 50 Essential Interview Questions (Mid Level).

19. How can a class implement multiple interfaces that declare members with the same signature?

If multiple interfaces declare members with the same signature, a single public implementation can satisfy all of them. 

This can be seen in the following example:

public interface IReadable
{
void Open();
}

public interface IWritable
{
void Open();
}

public class File : IReadable, IWritable
{
public void Open()
{
Console.WriteLine("Opening file...");
}
}

However, if each interface requires different behavior, C# allows explicit interface implementation. In this case, each interface member is implemented separately by qualifying the method with the interface name. The appropriate implementation is selected based on the interface through which the object is accessed.

public interface IReadable
{
void Open();
}

public interface IWritable
{
void Open();
}

public class File : IReadable, IWritable
{
void IReadable.Open()
{
Console.WriteLine("Opening file for reading...");
}

void IWritable.Open()
{
Console.WriteLine("Opening file for writing...");
}
}

20. What is the difference between virtual and abstract methods?

A virtual method provides a default implementation that derived classes may override if they need different behavior.

An abstract method has no implementation and can only be declared in an abstract class. Every non-abstract class derived from that class must provide its own implementation of the method.

For example, here Employee provides a default bonus calculation. Derived classes can override it if they need different behavior, but they are not required to do so.

public class Employee
{
public virtual decimal CalculateBonus()
{
return 1000m;
}
}

public class Manager : Employee
{
public override decimal CalculateBonus()
{
return 3000m;
}
}

On the other hand, here we can see an abstract method. A Shape cannot provide a meaningful default implementation of CalculateArea() because the formula depends on the specific shape. Therefore, the method is declared abstract, and every derived class must provide its own implementation.

public abstract class Shape
{
public abstract double CalculateArea();
}

public class Circle : Shape
{
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}

public double Radius { get; set; }
}

public class Rectangle : Shape
{
public override double CalculateArea()
{
return Width * Height;
}

public double Width { get; set; }
public double Height { get; set; }
}

21. What is method overloading vs. method overriding?

Method overloading and method overriding both allow multiple methods to share the same name, but they serve different purposes.

Method overloading means defining multiple methods with the same name but different parameter lists within the same class. The compiler determines which overload to call based on the arguments provided.

Method overriding allows a derived class to replace the implementation of a virtual or abstract method inherited from its base class. The implementation that is executed depends on the actual type of the object at runtime.

Overloading:

public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}

public int Add(int a, int b, int c)
{
return a + b + c;
}
}

Overriding:

public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}

public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}

22. What is the static keyword?

The static keyword indicates that a member belongs to the type itself rather than to a specific instance of that type. It can be applied to classes, methods, properties, fields, constructors, and other members.

A static class cannot be instantiated and can contain only static members. Static members are accessed through the class name instead of an object instance. They are commonly used for utility methods that do not depend on an object’s state.

For example, consider the Calculator class. Because Add() and Multiply() do not depend on any object-specific data, they are declared static and can be called directly through the Calculator class without creating an instance.

int sum = Calculator.Add(5, 3);
int product = Calculator.Multiply(5, 3);

public static class Calculator
{
public static int Add(int a, int b)
{
return a + b;
}

public static int Multiply(int a, int b)
{
return a * b;
}
}

23. Can we use “this” with a static class?

No. The this keyword refers to the current instance of a class. Because a static class cannot be instantiated, it has no instances, so there is no this reference available.

24. What is the difference between const and readonly fields?

Both const and readonly fields cannot be modified after initialization, but they differ in when their values are assigned.

A const field must be assigned a value at the point of declaration, and its value is known at compile time. A readonly field can be assigned either when it is declared or in the constructor, allowing each instance of a class to have a different value.

public class Employee
{
// Same for every Employee and known at compile time.
public const int MinimumAge = 18;

// Can be different for each Employee instance.
public readonly DateTime HireDate;

public Employee(DateTime hireDate)
{
HireDate = hireDate;
}
}

25. What is the difference between String and StringBuilder?

The primary difference is that String is immutable, whereas StringBuilder is mutable. Once a String object is created, its contents cannot be changed. Any operation that appears to modify a string actually creates a new String object. In contrast, StringBuilder modifies its existing internal buffer whenever possible, making it more efficient for repeated string modifications.

For example, each concatenation below creates a new String object:

string message = "Hello";

message += ", ";
message += "world";
message += "!";

When building a string incrementally, StringBuilder avoids creating a new object for every modification:

StringBuilder message = new();

message.Append("Hello");
message.Append(", ");
message.Append("world");
message.Append("!");

Console.WriteLine(message);

StringBuilder is particularly useful when constructing strings dynamically, such as inside loops:

StringBuilder report = new();

foreach (string name in names)
{
report.AppendLine(name);
}

string result = report.ToString();

In most everyday scenarios, String is the preferred choice because it is simple and easy to read. Consider using StringBuilder only when performing many string modifications, especially inside loops or when building large strings dynamically.

26. Explain the “continue” and “break” statement

Both break and continue are used to control the flow of loops.

The break statement immediately terminates the loop, and execution continues with the first statement after the loop.

The continue statement skips the remainder of the current iteration and immediately proceeds to the next iteration of the loop.

Example using break:

for (int i = 0; i < 6; i++)
{
if (i == 4)
{
break;
}

Console.WriteLine(i);
}

Output:

0
1
2
3

Example using continue:

for (int i = 0; i < 6; i++)
{
if (i == 4)
{
continue;
}

Console.WriteLine(i);
}

Output:

0
1
2
3
5

27. What are boxing and unboxing?

Boxing is the process of converting a value type into an object (or another reference type implemented by the value type). During boxing, the value is copied into a newly allocated object on the managed heap.

Unboxing is the reverse process. It extracts the value type from a boxed object. Because the runtime must verify the object’s actual type and copy the value back, unboxing requires an explicit cast.

Example of boxing:

int number = 42;

object boxed = number;

Example of unboxing:

object boxed = 42;

int number = (int)boxed;

Boxing and unboxing introduce additional allocations and copying, making them less efficient than working directly with value types. They most commonly occur when a value type is treated as an object or passed to APIs that expect object parameters. Modern C# code rarely requires explicit boxing because generic collections, such as List<T>, avoid it in most scenarios.

For example, storing integers in a non-generic ArrayList causes boxing:

ArrayList numbers = new ArrayList();

numbers.Add(1); // boxing
numbers.Add(2); // boxing

int value = (int)numbers[0]; // unboxing

Using a generic List<int> avoids both boxing and unboxing:

List<int> numbers = new();

numbers.Add(1);
numbers.Add(2);

int value = numbers[0];

28. What is a sealed class?

A sealed class is a class that cannot be inherited. Once a class is marked with the sealed keyword, no other class can derive from it.

Sealing a class communicates that its inheritance hierarchy is complete and prevents developers from extending or modifying its behavior through inheritance. This can simplify the design of a class and, in some cases, enable additional runtime optimizations.

For example:

public sealed class Logger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}

The following code would result in a compile-time error because Logger is sealed:

public class FileLogger : Logger
{
}

Use the sealed keyword when a class is not designed to be a base class. If a class is intended to be extended, leave it unsealed and carefully design its virtual members.

29. What is a partial class?

The partial keyword allows a class, struct, interface, or record to be split across multiple source files. During compilation, the compiler combines all parts into a single type.

The primary purpose of partial types is to separate developer-written code from automatically generated code. This allows tools such as Windows Forms Designer, WPF, source generators, and Entity Framework to generate part of a class without overwriting code written by the developer.

Although partial classes can also be used to split a large class across multiple files, this is generally discouraged. If a class has become so large that it needs to be divided into several files, it is often a sign that the class has too many responsibilities and should instead be refactored into smaller, more focused classes.

30. What is an enum?

An enum (enumeration) is a value type that represents a fixed set of named values. Enums make code more readable and maintainable by replacing numeric values or strings with meaningful names. They are commonly used when a variable can have only one value from a predefined set.

For example, instead of representing the status of an order with numbers such as 0, 1, and 2, you can define an enum:

public enum OrderStatus
{
Pending,
Processing,
Shipped,
Delivered,
Cancelled
}

The enum can then be used throughout the application:

OrderStatus status = OrderStatus.Processing;

if (status == OrderStatus.Shipped)
{
Console.WriteLine("The order has been shipped.");
}

Using an enum makes the code easier to understand and helps prevent invalid values from being assigned.

31. What is dependency injection?

Dependency injection (DI) is a design pattern in which a class receives its dependencies from the outside instead of creating them itself. This reduces coupling between classes, making applications easier to maintain, test, and extend.

The most common form of dependency injection in C# is constructor injection, where dependencies are passed to a class through its constructor. This makes a class’s dependencies explicit and ensures they are available when the object is created.

public interface ILogger
{
void Log(string message);
}

public class FileLogger : ILogger
{
public void Log(string message)
{
// Write to file
}
}

public class OrderService
{
private readonly ILogger _logger;

public OrderService(ILogger logger)
{
_logger = logger;
}

public void ProcessOrder()
{
_logger.Log("Order processed.");
}
}

In this example, OrderService depends only on the ILogger interface rather than a specific logger implementation. This allows different implementations, such as FileLogger or ConsoleLogger, to be provided without modifying OrderService.

Although constructor injection is the preferred approach in most C# applications, dependency injection can also be performed through properties or method parameters when appropriate.

32. What is the “using” statement?

The using statement simplifies resource management by ensuring that an object implementing IDisposable is automatically disposed when it is no longer needed. This guarantees that Dispose() is called even if an exception occurs, helping prevent resource leaks when working with files, database connections, streams, and other disposable resources.

For example:

using (StreamReader reader = File.OpenText("data.txt"))
{
string content = reader.ReadToEnd();
}

When execution leaves the using block, the reader’s Dispose() method is called automatically.

Since C# 8, the same behavior can be achieved using a using declaration, which does not require an additional scope:

using StreamReader reader = File.OpenText("data.txt");

string content = reader.ReadToEnd();

Both forms ensure that disposable resources are cleaned up automatically. The using declaration simply delays the call to Dispose() until the end of the current scope.

33. What are access modifiers?

Access modifiers control the visibility and accessibility of types and their members. They determine where a class, method, property, field, or other member can be accessed from.

C# provides the following access modifiers:

  • public – Accessible from any code that can access the containing type.
  • private – Accessible only within the containing type.
  • protected – Accessible within the containing type and by derived classes.
  • internal – Accessible only within the same assembly.
  • protected internal – Accessible either from the same assembly or from derived classes in another assembly.
  • private protected – Accessible only within the containing type or by derived classes in the same assembly.

For example:

public class Employee
{
public string Name { get; set; }

private decimal Salary { get; set; }

protected void CalculateBonus()
{
}

internal void Save()
{
}
}

34. What are delegates?

A delegate is a type that represents a reference to one or more methods with a specific signature. It allows methods to be passed as arguments, stored in variables, and invoked dynamically. Delegates are commonly used to implement callbacks, events, and LINQ operations.

For example, a method can accept a delegate instead of depending on a specific implementation:

public static void ProcessNumbers(
IEnumerable<int> numbers,
Action<int> processor)
{
foreach (int number in numbers)
{
processor(number);
}
}

The caller decides what should happen for each number by providing a different delegate:

ProcessNumbers(numbers, Console.WriteLine);

ProcessNumbers(numbers, number =>
{
Console.WriteLine(number * number);
});

In this example, ProcessNumbers is completely independent of the operation being performed. The behavior is supplied by the caller through a delegate.

Although you can define custom delegate types, modern C# commonly uses the built-in Action and Func delegates. Action represents a method that returns no value, while Func represents a method that returns a value. These delegates are widely used with lambda expressions, LINQ, and callback methods.

Action<string> print = Console.WriteLine;

Func<int, int, int> add = (a, b) => a + b; 

35. What is a multicast delegate?

A multicast delegate is a delegate that can reference multiple methods. When the delegate is invoked, all referenced methods are executed in the order they were added. Methods can be added using the += operator and removed using the -= operator. Multicast delegates are commonly used to implement events in C#.

public delegate void Notify();

Notify notify = SayHello;
notify += SayGoodbye;

notify();

Output:
Hello
Goodbye

36. What is an array?

An array is a collection of elements of the same type stored in a fixed-size sequence. Each element is identified by a zero-based index, allowing efficient access to individual values.

For example:

int[] marks = { 25, 34, 89 };

Console.WriteLine(marks[0]); // 25
Console.WriteLine(marks[2]); // 89

The example above uses a one-dimensional array, which is the most common type of array in C#. Arrays can also have multiple dimensions. For example, a two-dimensional array represents data arranged in rows and columns:

int[] marks = { 25, 34, 89 };

Console.WriteLine(marks[0]); // 25
Console.WriteLine(marks[2]); // 89

Unlike collections such as List<T>, the size of an array is fixed when it is created and cannot be changed later.

37. What is the difference between an array and a List<T>?

Both arrays and List<T> are collections that store elements of the same type, but they differ in how they manage their size.

An array has a fixed size that must be specified when it is created and cannot be changed later. A List<T>, on the other hand, automatically grows or shrinks as elements are added or removed, making it a more flexible choice for most applications.

Use an array when the number of elements is known in advance and will not change. Use List<T> when the collection needs to grow or shrink dynamically.

For example:

int[] numbers = { 1, 2, 3 };
List<int> numbers = new();

numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
numbers.Remove(2);

Arrays generally have slightly lower overhead and are appropriate for fixed-size collections, while List<T> is the preferred choice for most application code because of its flexibility and rich set of built-in methods.

38. What is IEnumerable<T>?

IEnumerable<T> is an interface that represents a sequence of elements that can be enumerated one at a time. It is the foundation of the foreach statement and is implemented by most collection types in .NET, including arrays, List<T>, and Dictionary<TKey, TValue>.

Because IEnumerable<T> represents a sequence rather than a specific collection, code that depends on it can work with many different collection types without modification. It is also the primary interface used by LINQ.

For example:

IEnumerable<string> names = new List<string>
{
"John",
"Jane",
"Alice"
};

foreach (string name in names)
{
Console.WriteLine(name);
}

Methods should generally accept IEnumerable<T> when they only need to read data from a collection, as this makes them more flexible and allows callers to pass in arrays, lists, or many other collection types.

39. What is a Dictionary<TKey, TValue>?

A Dictionary<TKey, TValue> is a collection that stores data as key-value pairs. Each key must be unique and is used to quickly retrieve its associated value. Dictionaries are useful when you need to look up data by an identifier rather than by its position in a collection.

For example:

Dictionary<int, string> employees = new()
{
{ 1, "John" },
{ 2, "Jane" },
{ 3, "Alice" }
};

Console.WriteLine(employees[2]);

Output:

Jane

Unlike a List<T>, which retrieves elements by their index, a Dictionary<TKey, TValue> retrieves values using a key. This makes dictionaries an excellent choice for scenarios such as looking up users by ID, products by SKU, or configuration values by name.

40. What is the difference between struct and class?

Classes and structs are both types used to model data in C#, but they have different semantics and are intended for different scenarios.

The most important difference is that classes are reference types, whereas structs are value types. Assigning a class to another variable copies only the reference, so both variables refer to the same object. Assigning a struct copies the entire value, creating an independent copy.

Classes are typically used to model entities with identity, such as customers, orders, or employees. Structs are best suited for small values that logically represent a single value, such as a point, a color, or a date.

For example, assigning a class copies the reference:

public class Person
{
public string Name { get; set; }
}

Person person1 = new() { Name = "John" };
Person person2 = person1;

person2.Name = "Jane";

Console.WriteLine(person1.Name); // Jane

Both variables refer to the same object, so modifying one affects the other. Assigning a struct copies its value:

public struct Point
{
public int X { get; set; }
public int Y { get; set; }
}

Point point1 = new() { X = 1, Y = 2 };
Point point2 = point1;

point2.X = 10;

Console.WriteLine(point1.X); // 1

Because point2 is a copy of point1, modifying it does not affect the original value.

As a general guideline, use a class when you need reference semantics or are modeling entities with identity. Use a struct for small, immutable values that conceptually represent a single value rather than an object.

41. What are extension methods?

Extension methods allow you to seemingly add new methods to an existing type without modifying its source code or creating a derived type. In reality, an extension method is simply a static method that can be called using instance method syntax.

Extension methods are declared in a static class, and their first parameter specifies the type being extended. This parameter is prefixed with the this keyword.

public static class StringExtensions
{
public static int WordCount(this string text)
{
return text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}

Although WordCount() appears to be an instance method of string, it is actually a static method:

string sentence = "Hello world from C#";
int count = sentence.WordCount();

Extension methods are commonly used to improve the readability of APIs and add reusable functionality to existing types. LINQ, for example, is built almost entirely on extension methods such as Where(), Select(), and OrderBy().

Starting with C# 14, extension methods remain fully supported, but the language also introduces extension members. This new syntax allows you to define extension methods, properties, and other members in a more natural and discoverable way, while preserving the same underlying behavior.

Advanced C# interview questions

42. What is the difference between “throw” and “throw ex”?

Both throw and throw ex are used to rethrow exceptions, but they differ in how they preserve the exception’s stack trace.

Using throw rethrows the original exception while preserving its complete stack trace. This allows you to see where the exception was originally thrown, making debugging much easier.

Using throw ex resets the stack trace so that it appears as though the exception originated at the point where it was rethrown. As a result, information about the original source of the exception is lost.

For this reason, throw should almost always be preferred when rethrowing an exception.

Correct:

try
{
// Some code
}
catch (Exception ex)
{
// Log the exception
throw;
}

Avoid:

try
{
// Some code
}
catch (Exception ex)
{
// Log the exception
throw ex;
}

If you need to wrap an exception with additional context, create a new exception and pass the original exception as the InnerException rather than using throw ex.

catch (Exception ex)
{
throw new OrderProcessingException(
"Failed to process the order.",
ex);
}

The “throw” statement will keep the original error stack of the previous function, while the “throw ex” statement will retain the stack trace from the throw point. Usually, it’s advised to use “throw” because it provides exact error information and trace data.

43. Can a try block have multiple catch blocks?

Yes. A single try block can be followed by multiple catch blocks, allowing different exception types to be handled differently.

More specific exception types should always be caught before more general ones. Otherwise, the more general catch block would handle the exception first, making the more specific handlers unreachable.

For example:

try
{
ProcessFile();
}
catch (FileNotFoundException)
{
Console.WriteLine("The file could not be found.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Access to the file was denied.");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}

As a general guideline, catch only the exceptions that your code can meaningfully handle. If a method cannot recover from an exception, it’s often better to let it propagate to the caller.

44. What is the difference between “finally” and “finalizer”?

These are two different concepts, though they sound similar:

  • Finally is a code block part of execution handling. This code block executes irrespective of whether the exception occurs or not.
  • Finalizer is a method called just before garbage collection. The compiler calls this method automatically when not called explicitly in code.

Thus, finally is related to execution handling, whereas finalizer is used relative to garbage collection.

45. What is the var keyword?

The var keyword instructs the compiler to infer the type of a local variable from the expression used to initialize it. The type is determined at compile time and remains fixed for the lifetime of the variable. Using var does not make the variable dynamically typed—it simply allows the compiler to infer the type instead of requiring it to be written explicitly.

For example, the following declarations are equivalent:

List<string> names = new List<string>();
var names = new List<string>();

In both cases, the variable has the type List<string>. The compiler replaces var with the inferred type during compilation, so there is no performance difference between the two declarations.

46. What is the dynamic type?

The dynamic type tells the compiler to defer member lookup and type checking until runtime. Unlike statically typed variables, the compiler does not verify whether methods or properties exist on a dynamic object. Instead, those operations are resolved when the application is running.

The dynamic type is primarily intended for interoperability with APIs whose types are not known at compile time, such as COM objects, objects loaded dynamically from external assemblies, or code written in dynamically typed languages.

For example:

dynamic calculator = pluginLoader.Load("CalculatorPlugin");
int result = calculator.Calculate(5, 10);

Although the compiler cannot verify that Calculate() exists, the call succeeds if the loaded object provides such a method. If it does not, an exception is thrown at runtime.

Because compile-time type checking is bypassed, dynamic should be used only when necessary. In most application code, strongly typed variables provide better safety, tooling support, and compile-time error checking.

47. What are anonymous types?

Anonymous types allow you to create an object without explicitly defining a class. The compiler automatically generates a type based on the properties specified during object creation.

Anonymous types are most commonly used to temporarily group related values, especially when projecting data with LINQ. Because the generated type has no explicit name, it is typically assigned to a variable declared with var.

For example:

var employee = new
{
FirstName = "John",
LastName = "Smith",
Department = "Sales"
};

Console.WriteLine(employee.FirstName);

Anonymous types are particularly useful with LINQ:

var employees = people
.Select(person => new
{
person.FirstName,
person.LastName
});

Anonymous types are intended for short-lived data structures. If a type needs to be reused across multiple methods or classes, defining a named class or record is usually a better choice.

48. What is multithreading?

A thread is an execution path within a process. Multithreading is the ability of a program to execute multiple threads concurrently, allowing different operations to make progress at the same time. This can improve application responsiveness and, for CPU-bound work, take advantage of multiple processor cores.

For example, one thread might process user input while another performs a lengthy calculation or loads data in the background. Without multithreading, one long-running operation could prevent other work from being performed.

Because multiple threads can access the same data simultaneously, multithreaded applications must be carefully designed to avoid problems such as race conditions and deadlocks.

Although .NET still provides the Thread class for manual thread management, modern applications more commonly use higher-level abstractions such as the Task Parallel Library (TPL), which simplifies concurrent programming by managing threads automatically. 

Bear in mind that asynchronous programming with async and await is a separate concept—it is primarily used to avoid blocking threads during long-running operations, such as I/O, and does not necessarily involve multiple threads.

49. What are async and await?

The async and await keywords simplify asynchronous programming in C#. They allow a method to start a long-running operation and resume execution when it completes without blocking the current thread while waiting.

The async modifier marks a method as asynchronous and allows it to use the await keyword. The await keyword asynchronously waits for a Task to complete, returning control to the caller until the operation finishes. Once the Task completes, execution resumes after the await statement.

Asynchronous programming is primarily used for I/O-bound operations, such as web requests, file access, and database queries. By avoiding blocked threads while waiting for these operations to complete, applications remain more responsive and can use system resources more efficiently.

public async Task<string> GetDataAsync()
{
using HttpClient client = new();

return await client.GetStringAsync(
"https://api.example.com/data");
}
public async Task ProcessDataAsync()
{
string data = await GetDataAsync();

Console.WriteLine(data);
}

It is important to note that asynchronous programming is not the same as multithreading. An asynchronous operation does not necessarily execute on a different thread. Instead, its primary purpose is to avoid blocking a thread while waiting for an operation, such as a network request or file I/O, to complete.

50. How is exception handling done in C#?

Exception handling is a mechanism for dealing with unexpected situations that occur while a program is running, such as invalid input, missing files, or network failures. Instead of terminating immediately when such an error occurs, a program can detect the problem, recover if possible, or report it appropriately.

In C#, errors are represented as exceptions—objects that are created when an unexpected condition occurs. When an exception is thrown, execution stops at the point where the error occurred, and the runtime searches for code capable of handling that exception. If no suitable handler is found, the exception continues to propagate up the call stack until it is handled or the application terminates.

C# provides several language constructs to support exception handling:

  • try: encloses code that may throw an exception.
  • catch: handles specific exceptions.
  • finally: executes cleanup code regardless of whether an exception occurred.
  • throw: throws a new exception or rethrows an existing one.

For example:

try
{
ProcessOrder();
}
catch (InvalidOperationException ex)
{
logger.LogError(ex, "Failed to process the order.");
throw;
}
finally
{
Console.WriteLine("Operation finished.");
}

As a general guideline, exceptions should only be caught when the code can meaningfully recover from the error or add useful context. Otherwise, they should be allowed to propagate to higher layers of the application.

51. What are custom exceptions?

Custom exceptions are user-defined exception types that represent errors specific to a particular application or business domain. Instead of throwing the general Exception type, custom exceptions make errors more descriptive and allow calling code to handle specific failure scenarios.

Custom exceptions are created by deriving from the Exception class:

public class InsufficientFundsException : Exception
{
public InsufficientFundsException(string message)
: base(message)
{
}
}

The custom exception can then be thrown when an application-specific error occurs:

public class BankAccount
{
public void Withdraw(decimal amount)
{
if (amount > Balance)
{
throw new InsufficientFundsException(
"The account does not have sufficient funds.");
}

Balance -= amount;
}

public decimal Balance { get; private set; }
}

Custom exceptions should be used only when the application needs to represent a meaningful domain-specific error. For general programming errors, existing .NET exception types, such as ArgumentException, InvalidOperationException, or InvalidDataException, are usually the better choice.

52. What is LINQ?

LINQ (Language Integrated Query) is a set of language features and library methods that allow you to query and transform data directly in C#. Instead of writing loops to filter, sort, or project data, you can express these operations using a concise, readable syntax.

LINQ works with many different data sources, including in-memory collections, databases, XML documents, and other data providers. Most LINQ operations work on sequences that implement IEnumerable<T>.

For example, the following query returns all names that start with the letter ‘J’:

List<string> names = new()
{
"John",
"Jane",
"Alice"
};

var result = names
.Where(name => name.StartsWith("J"));

LINQ supports two syntaxes. Method syntax is the most commonly used and is based on extension methods and lambda expressions:

var result = names
.Where(name => name.StartsWith("J"))
.OrderBy(name => name);

Query syntax provides an alternative syntax inspired by SQL:

var result =
from name in names
where name.StartsWith("J")
orderby name
select name;

Both examples produce the same result. In modern C# code, method syntax is used much more frequently because it is more flexible and supports all LINQ operations.


53. What is serialization?

Serialization is the process of converting an object into a format that can be stored or transmitted, such as JSON, XML, or a binary representation. Deserialization is the reverse process—it reconstructs an object from its serialized form.

Serialization is commonly used when sending data over a network, storing objects in files, or exchanging data between applications.

For example, the following object:

public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

Can be serialized to JSON:

Person person = new()
{
Name = "John",
Age = 30
};

string json = JsonSerializer.Serialize(person);

The resulting JSON might look like this:

{
"Name": "John",
"Age": 30
}

The JSON can later be deserialized back into a Person object:

Person person =
JsonSerializer.Deserialize<Person>(json)!;

Historically, .NET also supported custom serialization through interfaces such as ISerializable, which allowed developers to control exactly how an object was serialized and deserialized. However, this approach is now rarely used in modern applications. Today, most .NET applications rely on serializers such as System.Text.Json, which automatically serialize objects based on their public properties.

54. What are generics?

Generics are a language feature that allows classes, methods, interfaces, delegates, and other types to be written independently of the specific data types they operate on. Their primary purpose is to enable code reuse while maintaining compile-time type safety.

Without generics, developers often had to write multiple versions of the same code for different types or use non-generic collections such as ArrayList, which store objects and require casting. Generics eliminate these problems by allowing the type to be specified when the generic type or method is used.

For example, a generic repository can work with any entity type:

public class Repository<T>
{
private readonly List<T> _items = new();

public void Add(T item)
{
_items.Add(item);
}

public IEnumerable<T> GetAll()
{
return _items;
}
}

Generic type parameters can also be constrained to require certain capabilities. For example, a constraint can specify that a type must inherit from a particular base class, implement an interface, or provide a parameterless constructor:

Here’s a basic generic class example:

public class Repository<T>
where T : class, new()
{
}

Generics are widely used throughout .NET. Many of the framework’s most commonly used types, such as List<T>, Dictionary<TKey, TValue>, Task<T>, and IEnumerable<T>, are generic.


55. What is reflection?

Reflection is a .NET feature that allows a program to inspect information about assemblies, types, and their members at runtime. It can also be used to create objects dynamically and invoke methods whose types are not known at compile time.

Reflection is useful in scenarios where code must work with types discovered at runtime rather than being hardcoded. For example, frameworks often use reflection to discover classes, instantiate objects, or read metadata from attributes.

For example, the following code retrieves information about the properties of a type:

Type type = typeof(Person);

foreach (PropertyInfo property in type.GetProperties())
{
Console.WriteLine(property.Name);
}

Reflection is widely used by many .NET frameworks and libraries, including dependency injection containers, object-relational mappers (ORMs), serializers, and testing frameworks. Although powerful, reflection is generally slower than direct code execution and should be used only when its flexibility is required.

56. How do you use nullable types?

A nullable type is a type that can represent both its normal values and null. C# supports nullable value types and nullable reference types, which serve different purposes.

Value types (such as int, double, bool, and DateTime) cannot normally be null. By adding the ? suffix, you create a nullable value type that can also represent the absence of a value.

int? age = null;

if (age.HasValue)
{
Console.WriteLine(age.Value);
}

The null-coalescing operator (??) is commonly used to provide a default value when a nullable value type is null:

int? age = null;

int displayAge = age ?? 0;

Unlike value types, reference types have always been able to store null. Starting with C# 8, the ? annotation can be used to explicitly communicate that a reference is intended to be nullable. This doesn’t change the runtime behavior—it simply enables nullable reference type analysis, allowing the compiler to detect potential null-related issues.

string name = "John"; // Expected to never be null
string? middleName = null; // May be null

57. What is the parent class of all classes in C#?

This is a simple question to answer:

System.Object.

58. Explain code compilation in C#.

Before a C# application can run, the source code must be translated into instructions that the computer can execute. This process involves both compilation and runtime execution.

When you build a C# application, the C# compiler translates the source code into Intermediate Language (IL), a platform-independent instruction set. The IL, together with metadata describing the application’s types and members, is packaged into an assembly (a .dll or .exe file).

When the application starts, the Common Language Runtime (CLR) loads the assembly. As each method is executed for the first time, the Just-In-Time (JIT) compiler translates its IL into native machine code for the current operating system and processor. The generated machine code is then executed directly by the CPU.

The compilation and execution process can be summarized as follows:

  • The C# compiler translates the source code into Intermediate Language (IL).
  • The IL and metadata are packaged into an assembly (.dll or .exe).
  • The CLR loads the assembly when the application starts.
  • The JIT compiler converts IL into native machine code as methods are executed.
  • The CPU executes the generated machine code.

This approach allows the same compiled assembly to run on different platforms while still producing optimized native code for the target environment at runtime.

59. What is a HashSet?

A HashSet<T> is a collection that stores unique values. Unlike a Dictionary<TKey, TValue>, it stores only values rather than key-value pairs. If you try to add the same value more than once, the duplicate is ignored.

HashSet<T> provides very fast lookup, insertion, and removal operations, making it an excellent choice whenever you need to efficiently test whether a value exists in a collection or ensure that all values are unique.

For example:

HashSet<string> programmingLanguages = new()
{
"C#",
"Java",
"Python"
};

programmingLanguages.Add("C#"); // Ignored - already exists

bool containsCSharp =
programmingLanguages.Contains("C#");

Common use cases for HashSet<T> include removing duplicates from a collection, checking membership efficiently, and performing set operations such as union, intersection, and difference.

60. Why are strings immutable in C#?

Strings in C# are immutable, meaning that once a string object has been created, its contents cannot be changed. Any operation that appears to modify a string actually creates a new string object.

Immutability provides several important benefits:

  • It makes strings inherently thread-safe, since multiple threads can safely share the same string instance without synchronization.
  • It enables string interning, allowing identical string literals to share the same memory and reduce memory usage.
  • It ensures that strings can safely be used as keys in collections such as Dictionary<TKey, TValue>, because their hash code cannot change after they are created.
  • It prevents accidental modification of shared data, making programs easier to reason about.

For example:

string text = "Hello";
string updated = text + " World";

Console.WriteLine(text); // Hello
Console.WriteLine(updated); // Hello World

The original string is unchanged. Instead, a new string object containing “Hello World” is created. This is also why StringBuilder is often preferred when a string needs to be modified repeatedly.

61. What is the difference between Tuple and ValueTuple?

Both Tuple and ValueTuple allow multiple values to be grouped into a single object without creating a dedicated class. However, they differ significantly in their design and usage.

Tuple is a reference type introduced in .NET Framework 4. It exposes its values through properties such as Item1, Item2, and so on, making the code less readable.

Tuple<int, string> employee =
Tuple.Create(1, "John");

Console.WriteLine(employee.Item2);

ValueTuple, introduced in C# 7, is a value type that supports deconstruction and named elements, making it much more convenient to use.

(int Id, string Name) employee =
(1, "John");

Console.WriteLine(employee.Name);

ValueTuple can also be deconstructed into individual variables:

var (id, name) = employee;

In modern C# applications, ValueTuple is generally preferred because it provides better readability, supports named elements, and avoids heap allocation in many scenarios. The original Tuple type is now rarely used except when working with legacy code or APIs that explicitly require it.

62. What is the params keyword?

The params keyword allows a method to accept a variable number of arguments. This makes methods easier to call because the caller can pass individual values instead of explicitly creating a collection.

For example:

public static int Sum(params int[] numbers)
{
return numbers.Sum();
}

The same method can be called with any number of arguments:

int result1 = Sum(5, 10);
int result2 = Sum(5, 10, 15);
int result3 = Sum(5, 10, 15, 20);

Without the params keyword, the caller would have to create the collection explicitly:

int result = Sum(new[] { 5, 10, 15 });

A params parameter must be the last parameter in a method. Prior to C# 13, it had to be declared as a one-dimensional array. Starting with C# 13, params can also be used with other recognized collection types, such as ReadOnlySpan<T>, IEnumerable<T>, and several other collection types, enabling more flexible APIs and, in some cases, better performance.

.NET framework and platform questions

63. What is the difference between .NET Framework, .NET Core, and .NET?

NET Framework, .NET Core, and .NET are different generations of Microsoft’s .NET platform.

.NET Framework was the original implementation of .NET. It runs only on Windows and is now in maintenance mode, receiving bug fixes and security updates but no new features. It is still used by many existing applications but is generally not recommended for new development.

Microsoft later introduced .NET Core as a redesigned, open-source, cross-platform implementation of .NET. It was built to provide better performance, support modern application development, and run on Windows, Linux, and macOS.

After .NET Core 3.1, Microsoft simplified the product line by dropping the “Core” name. Since then, all new releases have simply been called .NET. In other words, today’s .NET is the direct successor to .NET Core, not a separate platform.

For new applications, the modern .NET platform is the recommended choice. .NET Framework is primarily maintained for existing applications that depend on Windows-specific technologies.

64. What is NuGet?

NuGet is a package manager for developers. It enables developers to share and consume useful code. A NuGet package is a single ZIP file that bears a .nupack or .nupkg filename extension and contains .NET assemblies and their needed files.

65. What are DLL files?

A DLL (Dynamic Link Library) is a compiled library that contains reusable code, resources, and other program components. Instead of placing all application code into a single executable, functionality can be organized into separate DLLs that can be referenced by one or more applications.

In .NET, DLL files typically contain assemblies that define classes, interfaces, methods, and other types. Applications reference these assemblies to reuse their functionality without copying the source code.

For example, an application might reference:

Company.Data.dll for database access
Company.Logging.dll for logging
Company.Utilities.dll for shared helper methods

Using DLLs promotes modularity and code reuse. It allows applications to be split into smaller, independently developed components that are easier to maintain, test, and share across multiple projects.

66. What is a POCO?

POCO (Plain Old CLR Object) is a simple C# class that is not tied to any specific framework or infrastructure. A POCO typically contains only data and business logic, without inheriting from framework base classes or requiring special attributes or interfaces.

POCOs are commonly used to represent domain entities, data transfer objects (DTOs), and configuration objects. Their simplicity makes them easy to test, reuse, and maintain.

For example:

public class Employee
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}

The term emphasizes that the class is “just a normal C# class” rather than one that depends on a particular framework.

67. What is a DTO?

A Data Transfer Object (DTO) is an object whose primary purpose is to transfer data between different parts of an application or between different applications. Unlike domain models, DTOs typically contain only data and little or no business logic.

DTOs are commonly used to exchange data between layers of an application, expose data through APIs, or communicate with external services. They help decouple the internal domain model from the data that is sent or received.

For example:

public class EmployeeDto
{
public int Id { get; set; }

public string FullName { get; set; } = string.Empty;
}

Design patterns and SOLID principles

68. What are the SOLID principles?

SOLID is a set of five object-oriented design principles that help developers write software that is easier to understand, maintain, extend, and test. While they are guidelines rather than strict rules, they are widely used when designing object-oriented applications.

  • Single Responsibility Principle (SRP): A class should have only one responsibility, meaning it should have only one reason to change.
  • Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification. New behavior should be added by extending existing code rather than changing code that already works.
  • Liskov Substitution Principle (LSP): A derived class should be usable anywhere its base class is expected without changing the correctness of the program.
  • Interface Segregation Principle (ISP): Clients should not be forced to depend on members they do not use. Prefer several small, focused interfaces over one large interface.
  • Dependency Inversion Principle (DIP): High-level and low-level modules should both depend on abstractions rather than concrete implementations.

Each of these principles is a broad topic in its own right and is typically discussed separately with practical examples.

69. What is the Singleton pattern?

The Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. It is typically used when a single shared object should be used throughout the lifetime of an application.

Common examples include application configuration, logging services, or in-memory caches.

A thread-safe implementation can be written using Lazy<T>:

public sealed class AppConfiguration
{
private static readonly Lazy<AppConfiguration> _instance =
new(() => new AppConfiguration());

private AppConfiguration()
{
}

public static AppConfiguration Instance => _instance.Value;

public string ConnectionString { get; } =
"...";
}

Although the Singleton pattern has valid use cases, it should be used with care. Because it introduces global state, it can make applications harder to test and increase coupling between components. In modern .NET applications, dependency injection is often a better alternative.

70. What is the Factory pattern?

The Factory pattern is a creational design pattern that encapsulates object creation in a dedicated class or method. Instead of creating objects directly with the new keyword, client code asks the factory to create the appropriate object.

This pattern is useful when the type of object to create depends on runtime conditions or when object creation is complex and should be centralized in one place. It also reduces coupling because client code depends on abstractions rather than concrete implementations.

For example:

public interface IVehicle
{
void Drive();
}

public class Car : IVehicle
{
public void Drive() =>
Console.WriteLine("Driving a car");
}

public class Motorcycle : IVehicle
{
public void Drive() =>
Console.WriteLine("Riding a motorcycle");
}

public class VehicleFactory
{
public IVehicle Create(string type)
{
return type switch
{
"car" => new Car(),
"motorcycle" => new Motorcycle(),
_ => throw new ArgumentException("Unknown vehicle type.")
};
}
}

The client code does not need to know which concrete class is instantiated—it simply works with the IVehicle interface returned by the factory.

Tips for preparing for your C# interview

Knowing the answers to common questions is just one part of interview preparation. Here are some practical steps to help you succeed:

  1. Practice coding problems: Use platforms like LeetCode or HackerRank to solve C# problems. Focus on data structures, algorithms, and LINQ queries.
  2. Know your experience level: Be honest about what you know. It’s better to say “I haven’t worked with that yet” than to bluff through an answer.
  3. Research the company’s tech stack: If they use ASP.NET Core, Entity Framework, or Azure, brush up on those technologies specifically.
  4. Build something: Having a personal project to discuss shows initiative. Even a simple CRUD application demonstrates practical skills.
  5. Prepare questions to ask: Asking about the team’s development practices, code review process, or tech stack shows genuine interest.
  6. Review your past work: Be ready to explain decisions you made in previous projects and what you learned from them.
  7. Practice explaining concepts aloud: Technical interviews often test communication as much as knowledge. Practice explaining OOP concepts to a friend or rubber duck.

Referring to the above questions and answers gives you in-depth knowledge of all essential concepts of the C# language. These technical answers can help sharpen your knowledge and increase your comprehension of the language. 

Need to brush up on your C# skills? Here are two of my Udemy courses that can help:

C# – 100 Coding Exercises — 100 bite-sized, in-browser coding challenges to sharpen your skills and build real muscle memory. No setup required — just open your browser and start solving, from the basics to advanced features.

Ultimate C# Masterclass for 2026 — A deep, hands-on dive into C# from the ground up: 47 hours of video, dozens of coding exercises, and real projects covering OOP, generics, LINQ, memory management, and more. Built to help you understand the language inside and out, not just memorize syntax.