← Back to Articles         
C# Coding Interview Questions - Part 3

C# Coding Interview Questions - Part 3

Published on 12 Oct 2026     5 min read C#
Real-Time C# Coding Questions

C# Coding Interview Questions – Advanced Concepts

1. How do you implement a thread-safe singleton?

Explanation: A Singleton ensures that only one instance of a class is created and shared throughout the application.

Use Lazy<T> to create a thread-safe singleton.

Example:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> instance =
        new(() => new Singleton());

    private Singleton()
    {
        Console.WriteLine("Instance Created");
    }

    public static Singleton Instance => instance.Value;

    public void ShowMessage()
    {
        Console.WriteLine("Hello Singleton");
    }
}

// Usage
Singleton obj1 = Singleton.Instance;
Singleton obj2 = Singleton.Instance;

Console.WriteLine(ReferenceEquals(obj1, obj2));

Output:

Instance Created
True

How it works:

Thread 1 ----+
             |
Thread 2 ----+----> Lazy<Singleton>
             |            |
Thread 3 ----+            v
                     One Instance
  • Lazy<T> creates the instance only when first accessed.

  • The private constructor prevents external object creation.

  • All threads receive the same instance.

Key Points:

  • sealed prevents inheritance.

  • Lazy<T> provides thread-safe initialization by default.

  • The instance is created only once.

  • Singleton initialization is thread-safe, but its methods and mutable state are not automatically thread-safe.

  • In ASP.NET Core, prefer Dependency Injection with AddSingleton<T>().

 

2. How do you execute multiple API calls concurrently using Task.WhenAll()? 🔴

Explanation: Task.WhenAll() allows multiple asynchronous operations to run concurrently instead of waiting for each one sequentially.

Example:

using System.Net.Http;

using HttpClient client = new HttpClient();

async Task<string> GetDataAsync(string url)
{
    return await client.GetStringAsync(url);
}

Task<string> task1 =
    GetDataAsync("https://jsonplaceholder.typicode.com/users");

Task<string> task2 =
    GetDataAsync("https://jsonplaceholder.typicode.com/posts");

Task<string> task3 =
    GetDataAsync("https://jsonplaceholder.typicode.com/comments");

string[] results = await Task.WhenAll(task1, task2, task3);

Console.WriteLine($"Users: {results[0].Length} characters");
Console.WriteLine($"Posts: {results[1].Length} characters");
Console.WriteLine($"Comments: {results[2].Length} characters");

How it works:

Start
  |
  +---- API 1 ----+
  |               |
  +---- API 2 ----+---- Task.WhenAll()
  |               |          |
  +---- API 3 ----+          v
                         All Complete
                              |
                              v
                         Process Results

All three API calls are started before awaiting their combined completion.

Key Points:

  • Task.WhenAll() waits asynchronously for all tasks to complete.

  • Tasks execute concurrently, not necessarily on separate threads.

  • Results are returned in the same order as the supplied tasks.

  • If any task fails, the combined task faults.

  • In ASP.NET Core, use IHttpClientFactory to manage HttpClient instances.

Performance Example:

Sequential:
API1 (2s) + API2 (3s) + API3 (1s)
Total ≈ 6 seconds

Concurrent:
API1 (2s) ----|
API2 (3s) --------|
API3 (1s) --|
Total ≈ 3 seconds

These timings are illustrative.

 

3. How do you flatten nested collections using LINQ? 🟡

Explanation: Use SelectMany() to combine multiple nested collections into a single collection.

Example:

var departments = new[]
{
    new
    {
        Name = "IT",
        Employees = new[] { "John", "Sam" }
    },
    new
    {
        Name = "HR",
        Employees = new[] { "David", "Mary" }
    }
};

var employees = departments
    .SelectMany(d => d.Employees);

foreach (var employee in employees)
{
    Console.WriteLine(employee);
}

Output:

John
Sam
David
Mary

How it works:

Departments
    |
    +---- IT
    |      John
    |      Sam
    |
    +---- HR
           David
           Mary
    |
    v
SelectMany()
    |
    v
John, Sam, David, Mary

Key Points:

  • Select() transforms each element.

  • SelectMany() transforms and flattens nested collections.

  • Useful for parent-child relationships.

  • Commonly used with Orders → OrderItems and Departments → Employees.

 

4. How do you implement an LRU cache? 🟡

Explanation: LRU (Least Recently Used) Cache removes the least recently accessed item when the cache reaches its capacity.

A common implementation uses a Dictionary and LinkedList.

Example:

class LRUCache
{
    private readonly int capacity;

    private readonly Dictionary<int, LinkedListNode<int>> map = new();
    private readonly LinkedList<int> list = new();

    public LRUCache(int capacity)
    {
        if (capacity <= 0)
            throw new ArgumentOutOfRangeException(nameof(capacity));

        this.capacity = capacity;
    }

    public void Put(int key)
    {
        if (map.TryGetValue(key, out var node))
        {
            list.Remove(node);
            list.AddFirst(node);
            return;
        }

        if (map.Count == capacity)
        {
            int oldest = list.Last!.Value;
            list.RemoveLast();
            map.Remove(oldest);
        }

        var newNode = list.AddFirst(key);
        map[key] = newNode;
    }

    public bool Get(int key)
    {
        if (!map.TryGetValue(key, out var node))
            return false;

        list.Remove(node);
        list.AddFirst(node);
        return true;
    }
}

// Usage
var cache = new LRUCache(3);

cache.Put(1);
cache.Put(2);
cache.Put(3);

cache.Get(1);  // Recently used

cache.Put(4);  // Removes 2

Console.WriteLine(cache.Get(2));
Console.WriteLine(cache.Get(1));

Output:

False
True

How it works:

Capacity = 3

Put(1), Put(2), Put(3)

Most Recent       Least Recent
    3 -> 2 -> 1

Get(1)

    1 -> 3 -> 2

Put(4)

    4 -> 1 -> 3

Removed: 2

Key Points:

  • Dictionary provides fast key lookup.

  • LinkedList maintains usage order.

  • Get() and Put() have O(1) average time complexity.

  • The example stores keys only; a production cache normally stores key-value pairs.

  • This implementation is not thread-safe.

  • Useful for limiting in-memory cache size.

 

5. How do you implement a custom generic collection? 🟡

Explanation: A custom generic collection allows storing different data types using a reusable class.

Use <T> to define a generic type parameter.

Example:

using System.Collections;
using System.Collections.Generic;

public class MyCollection<T> : IEnumerable<T>
{
    private readonly List<T> items = new();

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

    public bool Remove(T item)
    {
        return items.Remove(item);
    }

    public int Count => items.Count;

    public IEnumerator<T> GetEnumerator()
    {
        return items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// Usage
var numbers = new MyCollection<int>();

numbers.Add(10);
numbers.Add(20);
numbers.Add(30);

foreach (int number in numbers)
{
    Console.WriteLine(number);
}

Output:

10
20
30

How it works:

MyCollection<T>
       |
       +---- MyCollection<int>
       |       10, 20, 30
       |
       +---- MyCollection<string>
               "John", "Sam"
  • <T> represents the data type.

  • List<T> internally stores the elements.

  • IEnumerable<T> enables iteration using foreach.

  • GetEnumerator() returns an enumerator for the collection.

Key Points:

  • Generics provide compile-time type safety.

  • Avoid unnecessary boxing and unboxing for value types.

  • IEnumerable<T> supports iteration.

  • ICollection<T> provides a more complete collection contract.

  • Custom collections are useful when additional validation or specialized behavior is required.

 

Quick Interview Revision

Question

Important Concept

Key Point

Thread-safe Singleton

Lazy<T>

One instance, thread-safe initialization

Concurrent API calls

Task.WhenAll()

Runs async operations concurrently

Flatten collections

SelectMany()

Converts nested collections into one sequence

LRU Cache

Dictionary + LinkedList

O(1) average lookup and updates

Custom generic collection

IEnumerable<T>

Type-safe reusable collection

Interview Tip: For a Senior .NET Developer or Tech Lead interview, focus especially on thread safety, async concurrency, caching strategies, and generic interfaces. Interviewers may ask how these concepts are applied in ASP.NET Core Web API applications.

← Back to Articles