30 Interview questions that every C#/CSharp developer should know

Originally published on Medium · January 3, 2023

I run technical interviews for backend .NET roles, and I keep running into the same gap: candidates — including plenty with "senior" on their title — stumble on fundamentals they've been using for years. Below are 30 questions I actually ask, with the answers I'm listening for.

Photo by Christina @ wocintechchat.com on Unsplash

1. What is C# and .NET?

C# is a statically-typed, object-oriented programming language. .NET is a software framework that provides a runtime environment and a set of libraries for building and running applications on various platforms.

2. What are the main .NET features?

a) Runtime — responsible for executing .NET applications and provides services such as memory management, exception handling, and thread management.

b) A large class library: The .NET framework includes a vast collection of pre-built classes and types that provide a wide range of functionality, including file I/O, networking, data access, security, and more.

c) Garbage collection automatically reclaims memory occupied by unreachable unused objects.

d) Cross-platform support: The .NET framework can be used to build applications that run on a variety of platforms, including Windows, Linux, and macOS.

3. What are Access Modifiers?

Access modifiers are keywords that specify the accessibility of a class, method, or field. They determine which code is allowed to access the members that have been marked with a specific access modifier. There are six access modifiers in C#:

a) public: Members marked with the public access modifier are accessible from anywhere within the program.

b) private: Members marked with the private access modifier are only accessible within the class in which they are declared.

c) protected: Members marked with the protected access modifier are accessible within the class in which they are declared, and also within any derived classes.

d) internal: Members marked with the internal access modifier are accessible within the same assembly (i.e., executable or DLL file) in which they are declared.

e) protected internal: The type or member can be accessed by any code in the assembly in which it's declared, or from within a derived class in another assembly.

f) private protected: The type or member can be accessed by types derived from the class that are declared within its containing assembly.

4. Difference between Abstract class and an Interface

Abstract classes can have non-abstract methods and constructors, while interfaces only contain abstract members. However, since C# 8, interfaces can have default methods that provide concrete implementations within the interface.

A class can inherit only one abstract class, but it can implement multiple interfaces, making interfaces more flexible than abstract classes.

5. What is an Object?

An object is an instance of a class. A class is a blueprint that defines the properties and behaviors of an object. Objects are created from classes, and they contain the state and behavior defined by the class.

6. What is Inheritance?

Inheritance is a way to create a new class that is a modified version of an existing class. The new class is called the derived class, and the existing class is the base class. The derived class inherits the properties and behaviors of the base class and can also have additional properties and behaviors of its own.

7. What is Polymorphism?

Polymorphism is a feature in object-oriented programming languages that allows objects of different classes to have methods with the same name, but with different behaviors.

8. What is a Struct?

A struct is a value type. For structure-type variables, an instance of the type is copied.

9. What is a Delegate?

A delegate is a type that represents a reference to a method. Delegates are used to pass methods as arguments to other methods, which allows you to write more flexible and reusable code.

Example:

public delegate void PrintMessage(string message);

public class MessagePrinter
{
    public static void PrintUppercase(string message)
    {
        Console.WriteLine(message.ToUpper());
    }

    public static void PrintLowercase(string message)
    {
        Console.WriteLine(message.ToLower());
    }
}

public class Program
{
    static void Main()
    {
        PrintMessage printUppercase = MessagePrinter.PrintUppercase;
        printUppercase("Hello, World"); // Output: HELLO, WORLD

        PrintMessage printLowercase = MessagePrinter.PrintLowercase;
        printLowercase("Hello, World"); // Output: hello, world
    }
}

10. What is a Lambda Expression?

A lambda expression is a concise way to create anonymous functions in C#. It allows you to write a simple function without having to give it a name or define it as a separate method.

Example:

// Lambda expression that takes two integers and returns their sum
(int x, int y) => x + y

// Lambda expression that takes no arguments and returns void
() => Console.WriteLine("Hello, World")

11. What is a LINQ query in C#?

LINQ (Language-Integrated Query) is a set of features in C# that allows you to write queries over data sources in a syntax similar to SQL.

Example:

int[] numbers = { 1, 2, 3, 4 };
var evenNumbers = from n in numbers
    where n % 2 == 0
    select n;
foreach (var number in evenNumbers)
{
    Console.WriteLine(number);
}
// Output: 2 4

12. Static keyword

In C#, the static keyword is used to indicate that a member (such as a field, method, or property) belongs to the type itself rather than to a specific instance of the type.

Example:

public class Counter
{
    public static int Count = 10;
}

Example how to access the property:

var counterValue = Counter.Count;

13. What is the difference between a Const and Readonly field?

The const keyword is used to create a field that can't be modified after it's initialized. The value of a const field is determined at compile-time and can't be changed at runtime.

On the other hand, the readonly keyword is used to create a field that can't be modified after it's initialized, either through assignment or through the use of a mutator. However, unlike const, the value of a readonly field can be determined at runtime.

public class MyClass
{
    // A constant field.
    public const int MaxValue = 100;

    // A readonly field.
    public readonly int MinValue;
    public MyClass()
    {
        // Initialize the readonly field.
        MinValue = 0;
    }
}

14. What is the difference between a value type and a reference type?

Value types include built-in types such as int, float, and bool, as well as user-defined structs. When you create a value type variable, the value is stored directly in the memory allocated for the variable. Assigning a value type variable to another variable creates a copy of the value, so modifying one variable does not affect the other.

On the other hand, reference types include classes, interfaces, and delegates. When you create a reference type variable, the variable holds a reference to an object in memory, rather than the object itself. Assigning a reference type variable to another variable creates a copy of the reference, but both variables refer to the same object in memory. Modifying the object through one variable will affect the object as seen through the other variable.

Example:

int x = 5; // x is a value type
int y = x; // y is a copy of x
y = 10; // modifying y does not affect x
string s1 = "Hello"; // s1 is a reference type
string s2 = s1; // s2 is a copy of the reference to the string "Hello"
s2 = "World"; // s2 now refers to a different string, but s1 still refers to "Hello"

15. What is the difference between a Stack and a Heap?

A stack is a region of memory that stores method calls and local variables. The stack is used to store short-lived data that is only needed while a particular method is executing. When a method completes, the data on the stack is discarded.

A heap, on the other hand, is a region of memory that is used to store objects. The heap is used to store long-lived data that is shared between multiple methods and objects. Objects on the heap are dynamically allocated and are managed by the .NET runtime's garbage collector.

One key difference between the stack and the heap is that the stack is faster to access than the heap, because the data on the stack is organized in a Last In, First Out (LIFO) manner, which allows the processor to access it quickly. However, the stack has a limited size and can only store a limited amount of data. The heap, on the other hand, can store a much larger amount of data, but accessing data on the heap is slower than accessing data on the stack.

16. What is a generics type in C#?

Generics allow you to create types and methods that can work with any data type. This can make your code more flexible and reusable, because you don't have to specify a specific data type when you define your type or method. Instead, you can specify a placeholder type called a type parameter, which you can use to represent any data type when you use the type or method.

For example, you can use generics to create a generic List<T> class that can hold a list of elements of any data type. When you create an instance of List<T>, you can specify the type of elements that the list will hold, such as List<int> for a list of integers or List<string> for a list of strings.

17. What is the difference between using statement and Garbage Collector?

The using statement in C# is used to manage resources that implement the IDisposable interface. The using statement ensures that the Dispose method of the object is called when the block of code that uses the object is exited. This is useful for releasing resources that are being used by the object, such as file handles, network connections, and so on.

The garbage collector, on the other hand, is a component of the .NET runtime that is responsible for managing the memory used by your program. The garbage collector automatically reclaims memory that is no longer being used by your program, by periodically scanning the managed heap for objects that are no longer reachable by your program and then freeing the memory used by those objects.

18. What are break and continue statement in C#?

In C#, the break and continue statements are used to control the flow of execution in a loop.

The break statement is used to exit a loop early. When a break statement is encountered inside a loop, the loop is immediately terminated and control is transferred to the statement following the loop.

Example:

int i = 0;
while (true)
{
    if (i == 10)
    {
        break;
    }
    Console.WriteLine(i);
    i++;
}

In this example, the while loop will run indefinitely until the break statement is encountered. When i is equal to 10, the break statement is executed and the loop is terminated.

The continue statement, on the other hand, is used to skip the remaining statements in the current iteration of a loop and immediately proceed to the next iteration. When a continue statement is encountered inside a loop, control is transferred to the next iteration of the loop, skipping any remaining statements in the current iteration.

Example:

for (int i = 0; i < 10; i++)
{
    if (i % 2 == 1)
    {
        continue;
    }
    Console.WriteLine(i);
}

In this example, the continue statement is used to skip any iterations where i is odd. The loop will print only the even numbers from 0 to 9.

19. What is the difference between the string and StringBuilder classes in C#?

In C#, the string class represents an immutable sequence of Unicode characters, while the StringBuilder class represents a mutable sequence of characters. This means that once you create a string object, you cannot modify its value. On the other hand, you can use the StringBuilder class to build a string by appending, inserting, or deleting characters from the string.

Here are some key differences between string and StringBuilder:

Immutability: As mentioned above, string objects are immutable, which means that once you create a string object, you cannot modify its value. On the other hand, StringBuilder objects are mutable, which means you can append, insert, or delete characters from the string.

Performance: Because string objects are immutable, any operation that modifies a string object actually creates a new string object with the modified value. This can be inefficient if you need to perform many modifications on a string, because it will create a new object each time. StringBuilder objects, on the other hand, can be modified in place, which can be more efficient if you need to perform many modifications on a string.

Use cases: string is generally preferred when you have a fixed string that you want to use multiple times, because it can be stored in memory and reused. StringBuilder is generally preferred when you need to build a string dynamically, because it allows you to modify the string in place without creating a new object each time.

Example:

using System;
using System.Text;
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Using string
            string greeting = "Hello, ";
            greeting += "world!";
            Console.WriteLine(greeting);

            // Using StringBuilder
            StringBuilder sb = new StringBuilder();
            sb.Append("Hello, ");
            sb.Append("world!");
            Console.WriteLine(sb.ToString());

            //Output:
            //Hello, world!
            //Hello, world!
        }
    }
}

20. What is a nullable type in C#?

In C#, a nullable type is a value type that can be assigned a value or the special value null, which indicates the absence of a value or a null reference.

By default, value types in C# (such as int, double, bool, etc.) cannot be assigned the value null. This is because they are designed to always hold a value, and null is not considered a valid value for a value type.

21. What is the difference between a task and a thread in C#?

In C#, a Task is a lightweight object that represents the execution of a unit of work. It is a part of the Task Parallel Library (TPL), which is a set of public types and APIs in the System.Threading.Tasks namespace that simplify the process of adding parallelism and concurrency to applications.

A Thread is a lower-level object that represents a running thread of execution. It is a part of the System.Threading namespace, which provides a set of classes and interfaces that enable multithreaded programming.

Here are some key differences between Task and Thread:

a) Execution model: A Task is executed as part of a task scheduler, which is responsible for starting and running the task on a thread pool thread. A Thread is executed directly on a thread from the operating system's thread pool.

b) Abstraction level: Task provides a higher-level abstraction over threads, making it easier to write concurrent and parallel code. Thread provides a lower-level abstraction, giving you more control over the execution of the thread, but also requiring you to handle certain aspects of thread management yourself.

c) Exception handling: Task has built-in support for exception handling, allowing you to specify a continuation that executes when the task completes with an exception. With Thread, you have to manually handle exceptions that occur on the thread.

d) Cancellation: Task provides built-in support for cancellation through the CancellationToken class. With Thread, you have to manually implement cancellation support.

22. What is a lock statement in C#?

The lock statement is used to synchronize access to a block of code among multiple threads. It ensures that only one thread can execute the code block at a time, while other threads that try to enter the block are suspended until the lock is released. This can be useful for preventing race conditions and data corruption when multiple threads are accessing shared resources.

Example:

object lockObject = new object();
void SomeMethod()
{
    lock (lockObject)
    {
        // Code in this block will be executed by only one thread at a time
    }
}

23. What is an async/await method in C#?

In C#, the async and await keywords are used to implement asynchronous programming. Asynchronous programming allows you to perform long-running tasks, such as network operations or file I/O, without blocking the current thread. This can improve the responsiveness and scalability of your application, as it allows other tasks to be performed while the asynchronous operation is in progress.

The async keyword indicates that the method contains asynchrony. It allows you to use the await keyword inside the method. The await keyword is used to suspend the execution of the method until a task completes. When the await keyword is applied to the task, it causes the method to pause until the task completes, and then it retrieves the result of the task.

24. What is an Enum in C#?

In C#, an enum (short for "enumeration") is a value type that represents a set of named constants. An enum is defined using the enum keyword, followed by a name and a list of named constants in curly braces.

Example:

enum DaysOfWeek
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

25. What is JIT compiler?

"JIT" stands for "Just-In-Time". The C# Just-In-Time (JIT) compiler is a component of the .NET runtime that is responsible for converting compiled C# code into machine code that can be executed by the processor.

The JIT compiler is used when a C# application is run for the first time. When the application is started, the JIT compiler converts the compiled code into machine code and stores it in memory. This process is called "JIT compilation".

26. What is the difference between Float, Double and Decimal?

float, double, and decimal are three different types of floating point numbers, which are used to represent non-integer numeric values.

Here is a summary of the main differences between these types:

float: The float type represents a single-precision floating point number. It uses 4 bytes (32 bits) of memory and has a range of approximately +/- 1.5 x 10^-45 to +/- 3.4 x 10³⁸, with a precision of about 7 decimal digits.

double: The double type represents a double-precision floating point number. It uses 8 bytes (64 bits) of memory and has a range of approximately +/- 5.0 x 10^-324 to +/- 1.7 x 10³⁰⁸, with a precision of about 15–16 decimal digits.

decimal: The decimal type represents a high-precision floating point number. It uses 16 bytes (128 bits) of memory and has a range of approximately +/- 1.0 x 10^-28 to +/- 7.9 x 10²⁸, with a precision of 28–29 decimal digits.

In general, float is the smallest and fastest of the three types, but it has the least precision. double is slightly larger and slower than float, but it has more precision. decimal is the largest and slowest of the three types, but it has the most precision.

When choosing which type to use, you should consider the range and precision requirements of your application. If you need a very small range and high precision, you should use decimal. If you need a larger range and less precision, you should use double. If you need the smallest range and lowest precision, you can use float.

27. What is the difference between IEnumerable, Array and List?

IEnumerable is an interface that defines a single method, GetEnumerator, which returns an object that implements the IEnumerator interface. This allows you to iterate over a collection of objects. Since you can only iterate over the items it makes the IEnumerable interface a readonly collection.

An array is a collection of items that are stored in a contiguous block of memory. Arrays are fixed-size, which means you must specify the size of the array when you create it, and you cannot change the size of the array after it is created.

List is a generic collection class in the System.Collections.Generic namespace. It provides a flexible and convenient way to store and manipulate a list of objects. Unlike an array, a List can be resized dynamically, which means you can add or remove elements from the list after it is created.

In summary, IEnumerable is an interface that allows you to iterate over a collection of objects, while an array and a List are both concrete classes that you can use to store and manipulate collections of objects.

28. What is a Tuple?

A tuple is a data structure that represents a set of values, where each value can be of a different type. Tuples are similar to structs, but they are immutable, which means that once you create a tuple, you cannot change the values of its elements.

Tuples are useful when you need to return multiple values from a method, or when you want to store a small number of values with different types in a lightweight data structure.

Example:

// Using the Tuple class
Tuple<int, string, bool> myTuple = new Tuple<int, string, bool>(1, "hello", true);
// Using the ( ) operator
var myTuple = (1, "hello", true);

29. What is Reflection?

Reflection is a feature of the .NET framework that allows you to inspect and manipulate the types, members, and assemblies in your code at runtime.

Reflection is a powerful tool that can be used to perform tasks that would be difficult or impossible to do using other means. However, it is generally slower than using the standard APIs, so it should be used sparingly and only when necessary.

30. What is the difference between Func, Action and Predicate?

In C#, Func, Action, and Predicate are all delegate types that can be used to represent methods.

A Func delegate represents a method that takes one or more input parameters and returns a value. The input parameters and the return type are specified as generic type arguments when you declare a Func delegate. For example:

Func<int, int, int> add = (x, y) => x + y;

This declares a Func delegate named add that takes two int parameters and returns an int. The lambda expression (x, y) => x + y specifies the body of the method.

An Action delegate represents a method that takes one or more input parameters and does not return a value. The input parameters are specified as generic type arguments when you declare an Action delegate. For example:

Action<string, int> print = (s, i) => Console.WriteLine($"{s}: {i}");

This declares an Action delegate named print that takes a string and an int parameter and does not return a value. The lambda expression (s, i) => Console.WriteLine($"{s}: {i}") specifies the body of the method.

A Predicate delegate represents a method that takes one input parameter and returns a bool value. The input parameter is specified as a generic type argument when you declare a Predicate delegate.

For example:

Predicate<int> isEven = x => x % 2 == 0;

This declares a Predicate delegate named isEven that takes an int parameter and returns a bool. The lambda expression x => x % 2 == 0 specifies the body of the method.

In summary, Func delegates represent methods that return a value, Action delegates represent methods that do not return a value, and Predicate delegates represent methods that return a bool value.

Hope this helps!