Menu
CoddyTech

C# Interview Questions and Answers

C# and .NET questions grouped by topic and level. The runnable examples use C# 7 and run on Mono; C# 8 to 14 features are shown as code.

89 questions13 output quizzesRunnable code checked on C# 7 on Mono 6.12 (newer C# shown as code)By Kevin Spektor, Co-founder & CTO

C# interview questions for freshers

Types, operators, strings, nullable values and exceptions, the basics of every campus and junior round.

What is C# and how is it related to .NET?

Fresherbasics

C# is a statically typed, object-oriented language from Microsoft. The compiler turns it into IL (intermediate language), and the .NET runtime (the CLR) compiles that IL to machine code when the program runs. .NET is the platform around the language: the runtime, the base class library and the SDK. More in What is C#.

What are the built-in data types in C#?

Fresherdata types

C# has value types for numbers, characters and booleans (int, long, double, decimal, bool, char), plus string and object as built-in reference types. Each keyword aliases a .NET type: int is System.Int32. Use decimal for money.

C#
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine(sizeof(int) + " " + sizeof(long) + " " + sizeof(decimal) + " " + sizeof(char));
        Console.WriteLine(int.MaxValue);
        Console.WriteLine(typeof(int).FullName);
    }
}

Trap: char is 2 bytes, one UTF-16 code unit, not a byte as in C.

What is the difference between string and String in C#?

Fresherdata types

There is no difference: string is the C# keyword for System.String, and both compile to the same IL, as do int and Int32. Use the keyword in declarations. String needs using System;; string never does.

What does this print: integer vs floating-point division?

Fresheroperators
C#
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine(7 / 2 + " " + 7 / 2.0 + " " + 7 % 3);
    }
}

Predict the output

7 / 2 divides two int values, so the fraction is dropped: 3. With one double operand (2.0) the division is floating point: 3.5. 7 % 3 is the remainder, 1.

For an average, cast first: (double)sum / count, not (double)(sum / count), which has already truncated.

What does this print: postfix and prefix increment in one expression?

Fresheroperators
C#
using System;

class Program
{
    static void Main()
    {
        int i = 5;
        Console.WriteLine(i++ + ++i);
    }
}

Predict the output

C# evaluates operands left to right, so the result is defined (in C it is undefined behavior). i++ yields 5 and sets i to 6; ++i sets i to 7 and yields 7. The sum is 12.

What is the difference between const and readonly in C#?

Fresherbasics

A const is a compile-time constant, implicitly static, limited to numbers, bool, char, string, enums and null. A readonly field can be any type and is set once at run time, in its declaration or a constructor.

class Config
{
    public const int MaxUsers = 100;              // baked into callers at compile time
    public static readonly DateTime Started = DateTime.Now; // computed at runtime
    public readonly Guid Id;

    public Config() { Id = Guid.NewGuid(); }       // readonly can be set here
}

Trap: a public const is copied into every assembly that uses it, so callers keep the old value until they are recompiled. Use static readonly for values that might change.

What is the difference between ref and out parameters?

Freshermethods

Both pass a variable by reference. A ref argument must be initialized before the call; an out parameter need not be, but the method must assign it before returning.

C#
using System;

class Program
{
    static void Double(ref int x) { x *= 2; }

    static bool TryHalf(int n, out int half)
    {
        half = n / 2;
        return n % 2 == 0;
    }

    static void Main()
    {
        int value = 21;
        Double(ref value);
        Console.WriteLine(value);

        if (TryHalf(value, out int h))
            Console.WriteLine("half is " + h);
    }
}

out is the pattern behind int.TryParse. See ref and out.

What are tuples in C#, and how do you return multiple values from a method?

Fresherdata types

C# 7 tuples (System.ValueTuple) group a few values without declaring a type, such as (int min, int max), and are the usual way to return several values from a private method.

C#
using System;

class Program
{
    static (int min, int max) MinMax(int[] nums)
    {
        int min = nums[0], max = nums[0];
        foreach (int n in nums)
        {
            if (n < min) min = n;
            if (n > max) max = n;
        }
        return (min, max);
    }

    static void Main()
    {
        var r = MinMax(new[] { 4, 9, 1, 7 });
        Console.WriteLine(r.min + " " + r.max);

        var (lo, hi) = MinMax(new[] { 3, 8 });   // deconstruction
        Console.WriteLine(lo + " " + hi);

        int a = 1, b = 2;
        (a, b) = (b, a);                        // swap without a temp variable
        Console.WriteLine(a + " " + b);
    }
}

Trap: the element names exist only at compile time; at run time the fields are Item1, Item2. See tuples.

What does this print: == on strings vs on object?

Fresherstrings
C#
using System;

class Program
{
    static void Main()
    {
        string a = "hello";
        string b = new string("hello".ToCharArray());
        Console.WriteLine((a == b) + " " + ((object)a == (object)b) + " " + a.Equals(b));
    }
}

Predict the output

== on two string variables is overloaded to compare characters: True. Cast to object, == compares references, and new string(...) made a second object: False. Equals is virtual and String overrides it to compare values: True. == is chosen at compile time from the static types.

Why are strings immutable in C#, and when should you use StringBuilder?

Fresherstrings

A string never changes after it is created; +, Replace and ToUpper return new strings, which makes strings safe to share between threads. Use StringBuilder to build a string in a loop, because it appends into one buffer instead of copying the whole text each step.

C#
using System;
using System.Text;

class Program
{
    static void Main()
    {
        string s = "abc";
        string t = s.ToUpper();
        Console.WriteLine(s + " " + t);

        var sb = new StringBuilder();
        for (int i = 1; i <= 5; i++)
            sb.Append(i).Append(',');
        sb.Length--;              // drop the last comma
        Console.WriteLine(sb.ToString());
    }
}

See StringBuilder.

What is boxing and unboxing in C#?

Freshertypes

Boxing copies a value type into a new heap object so it can be used as object or an interface; unboxing copies it back out with a cast. Both cost time in hot loops.

C#
using System;

class Program
{
    static void Main()
    {
        int n = 42;
        object boxed = n;        // boxing: new object on the heap
        int back = (int)boxed;   // unboxing: type check and copy
        Console.WriteLine(back);

        try
        {
            long wrong = (long)boxed;  // must unbox to the exact type
            Console.WriteLine(wrong);
        }
        catch (InvalidCastException)
        {
            Console.WriteLine("InvalidCastException");
        }
    }
}

Trap: unboxing must use the exact boxed type, so a boxed int becomes a long with (long)(int)boxed.

What does this print: == and Equals on two boxed ints?

Freshertypes
C#
using System;

class Program
{
    static void Main()
    {
        object a = 1;
        object b = 1;
        Console.WriteLine((a == b) + " " + a.Equals(b));
    }
}

Predict the output

Each assignment boxes the 1 into a separate object. == on two object variables compares references: False. Equals is virtual, and the boxed Int32 compares values: True. Compare values that travel as object with Equals.

What are nullable types, and what do ?, ?? and ?. do?

Freshertypes

int? (Nullable<int>) is a value type that can also hold null. ?? returns the right side when the left side is null; ?. calls a member only on a non-null object and gives null otherwise.

C#
using System;

class Program
{
    static void Main()
    {
        int? age = null;
        Console.WriteLine(age.HasValue);
        Console.WriteLine(age ?? 18);

        string name = null;
        int? length = name?.Length;
        Console.WriteLine(length == null);
        Console.WriteLine(name?.Length ?? 0);
    }
}

Trap: .Value on a null int? throws InvalidOperationException. See nullable types.

What is the difference between var, dynamic and object?

Freshertypes

var lets the compiler infer the static type, so the variable stays strongly typed. object is the base type of everything and needs casts. dynamic skips compile-time checks and resolves members at run time.

var count = 10;          // count is an int; count = "x" does not compile
object o = 10;           // int boxed as object; o + 1 does not compile
int n = (int)o + 1;      // a cast is needed

dynamic d = 10;
d = d + 1;               // resolved at runtime
d.Foo();                 // compiles, throws RuntimeBinderException when run

Use dynamic only for COM interop or truly dynamic data.

What are the access modifiers in C#?

Fresheroop

C# has six access levels; the defaults are private for class and struct members and internal for top-level types. C# 11 also adds file, which limits a top-level type to its source file (see access modifiers).

ModifierAccessible from
publicanywhere
privatethe same class
protectedthe class and derived classes
internalthe same assembly
protected internalsame assembly or derived classes
private protectedderived classes in the same assembly

Trap: internal means the assembly, not the namespace.

What does this print: incrementing int.MaxValue?

Fresheroperators
C#
using System;

class Program
{
    static void Main()
    {
        int x = int.MaxValue;
        x++;
        Console.WriteLine(x);
    }
}

Predict the output

Integer arithmetic is unchecked by default (unless the project sets CheckForOverflowUnderflow), so adding 1 to int.MaxValue wraps to int.MinValue, -2147483648, with no exception. A checked block or checked(x + 1) throws OverflowException instead. A constant expression such as int.MaxValue + 1 is a compile error.

What does this print: return inside try with a finally?

Fresherexceptions
C#
using System;

class Program
{
    static int F()
    {
        try
        {
            return 1;
        }
        finally
        {
            Console.Write("finally ");
        }
    }

    static void Main()
    {
        Console.WriteLine(F());
    }
}

Predict the output

finally runs whenever control leaves the try block, including through return. The value 1 is computed, finally writes finally , and then the method returns, so Main prints 1 after it. A return inside finally is a compile error in C#.

OOPs in C# interview questions

Classes, inheritance, polymorphism, interfaces and the keywords virtual, override, new and sealed.

What are the four pillars of OOP, and how does C# support each?

Fresheroop

Encapsulation (private fields behind properties), inheritance (one base class, any number of interfaces), polymorphism (virtual and override at run time, overloading at compile time) and abstraction (abstract classes and interfaces, which define what a type does without fixing how).

What is the difference between an abstract class and an interface in C#?

Fresheroopinterfaces

An abstract class is a partial implementation that a class inherits from (only one); an interface is a contract that a class or struct implements (any number). Only an abstract class can have constructors and instance fields.

Use an abstract class when related types share code and state (Stream), an interface for a capability unrelated types share (IDisposable). Since C# 8 interfaces can have default method bodies. See interfaces.

What does this print: override vs new through a base class reference?

Experiencedooppolymorphism
C#
using System;

class A
{
    public virtual string Who() { return "A"; }
    public string Name() { return "A"; }
}

class B : A
{
    public override string Who() { return "B"; }
    public new string Name() { return "B"; }
}

class Program
{
    static void Main()
    {
        A x = new B();
        Console.WriteLine(x.Who() + x.Name());
    }
}

Predict the output

Who is virtual and overridden, so the call dispatches on the runtime type and returns "B". Name is hidden with new, and a hidden method is chosen by the variable's compile-time type (A), so it returns "A". Without new you get warning CS0108 and the same behavior.

What is the difference between method overloading and overriding?

Fresherooppolymorphism

Overloading is several methods with one name and different parameters, chosen by the compiler. Overriding is a derived class replacing a virtual or abstract method with the same signature, chosen at run time.

C#
using System;

class Shape
{
    public virtual double Area() { return 0; }
}

class Circle : Shape
{
    public double R;
    public Circle(double r) { R = r; }
    public override double Area() { return Math.Round(Math.PI * R * R, 2); }
}

class Program
{
    static string Describe(int x) { return "int " + x; }
    static string Describe(string s) { return "string " + s; }

    static void Main()
    {
        Console.WriteLine(Describe(5));
        Console.WriteLine(Describe("five"));

        Shape s = new Circle(2);
        Console.WriteLine(s.Area());
    }
}

Trap: methods that differ only by return type are not overloads; they do not compile.

What does this print: static, base and derived constructors over two instances?

Experiencedoopconstructors
C#
using System;

class Base
{
    public Base() { Console.Write("Base "); }
}

class Derived : Base
{
    static Derived() { Console.Write("Static "); }
    public Derived() { Console.Write("Derived "); }
}

class Program
{
    static void Main()
    {
        new Derived();
        new Derived();
        Console.WriteLine();
    }
}

Predict the output

The static constructor runs once, before the first instance. Each new Derived() then runs the base constructor before the derived one, and the second new skips the static constructor. An exception in a static constructor leaves the type unusable (TypeInitializationException).

What is the difference between a field and a property in C#?

Fresheroopproperties

A field is a variable stored in the object; a property is a pair of get and set methods that reads like a field. Expose public data as properties, so you can add validation later without breaking callers.

C#
using System;

class Account
{
    private decimal balance;               // field

    public string Owner { get; set; }       // auto-property
    public decimal Balance                  // property with logic
    {
        get { return balance; }
        private set
        {
            if (value < 0) throw new ArgumentException("negative");
            balance = value;
        }
    }

    public void Deposit(decimal amount) { Balance += amount; }
}

class Program
{
    static void Main()
    {
        var acc = new Account { Owner = "Asha" };
        acc.Deposit(250m);
        Console.WriteLine(acc.Owner + " " + acc.Balance);
    }
}

What does this print: copying a struct vs copying a class?

Freshertypesoop
C#
using System;

struct P { public int X; }
class C { public int X; }

class Program
{
    static void Main()
    {
        var p1 = new P { X = 1 };
        var p2 = p1;
        p2.X = 2;

        var c1 = new C { X = 1 };
        var c2 = c1;
        c2.X = 2;

        Console.WriteLine(p1.X + " " + c1.X);
    }
}

Predict the output

P is a struct, a value type, so var p2 = p1 copies the value and p1.X stays 1. C is a class, so c1 and c2 point at one object and c1.X becomes 2. Use structs for small, immutable, value-like data such as DateTime.

What does the sealed keyword do in C#?

Experiencedoop

A sealed class cannot be inherited, and a sealed override method cannot be overridden further down the hierarchy.

public sealed class Token { }          // class Special : Token { }  -> compile error CS0509

class Base { public virtual void Run() { } }
class Middle : Base { public sealed override void Run() { } }
class Leaf : Middle { /* cannot override Run here */ }

Seal classes you did not design for extension; the JIT can also call a sealed type's methods without a virtual call.

What is a static class in C#, and when do you use static members?

Fresheroop

A static class cannot be instantiated or inherited and holds only static members; use it for stateless helpers such as Math and for extension methods. A static member belongs to the type, so all callers share one copy.

C#
using System;

static class Temperature
{
    public static double ToFahrenheit(double c) { return c * 9 / 5 + 32; }
}

class Counter
{
    public static int Created;     // one copy shared by every instance
    public int Id;
    public Counter() { Created++; Id = Created; }
}

class Program
{
    static void Main()
    {
        Console.WriteLine(Temperature.ToFahrenheit(100));
        var a = new Counter();
        var b = new Counter();
        Console.WriteLine(a.Id + " " + b.Id + " " + Counter.Created);
    }
}

It prints 212, then 1 2 2: Id is per object, Created is shared. See static.

What is explicit interface implementation, and when do you need it?

Experiencedinterfaces

Explicit implementation names the member InterfaceName.Member, so it is callable only through that interface type. Use it when two interfaces declare the same member with different meanings, or to keep a member off the class's public surface.

C#
using System;

interface IPrinter { string Print(); }
interface IExporter { string Print(); }

class Report : IPrinter, IExporter
{
    string IPrinter.Print() { return "sent to printer"; }
    string IExporter.Print() { return "exported as PDF"; }
}

class Program
{
    static void Main()
    {
        var r = new Report();
        // r.Print();  does not compile: no public Print
        Console.WriteLine(((IPrinter)r).Print());
        Console.WriteLine(((IExporter)r).Print());
    }
}

What are extension methods, and how do you write one?

Experiencedmethods

An extension method is a static method in a static class whose first parameter is marked this; callers use it like an instance method. It adds behavior to types you cannot change, and LINQ is built this way.

C#
using System;

static class StringExtensions
{
    public static bool IsPalindrome(this string s)
    {
        int i = 0, j = s.Length - 1;
        while (i < j)
        {
            if (s[i] != s[j]) return false;
            i++;
            j--;
        }
        return true;
    }
}

class Program
{
    static void Main()
    {
        Console.WriteLine("level".IsPalindrome());
        Console.WriteLine("coddy".IsPalindrome());
    }
}

An instance method with the same signature wins. See extension methods.

What is the difference between is and as in C#?

Experiencedtypesoop

is tests type compatibility and returns bool (since C# 7 it can also declare a typed variable); as converts and returns null on failure, where a cast (T)x throws InvalidCastException.

C#
using System;

class Program
{
    static void Describe(object o)
    {
        if (o is int n)
            Console.WriteLine("int, doubled: " + n * 2);

        string s = o as string;
        Console.WriteLine(s == null ? "not a string" : "string of length " + s.Length);
    }

    static void Main()
    {
        Describe(21);
        Describe("hello");
    }
}

Trap: o as int does not compile, because as needs a reference or nullable type.

.NET interview questions: CLR, GC, value and reference types

How a C# program runs: the CLR, the JIT, memory, garbage collection and IDisposable.

What is the CLR, and how does a C# program run?

Fresher.net

The CLR (Common Language Runtime) is the .NET virtual machine: it loads assemblies, JIT-compiles their IL to native code, runs the garbage collector and enforces type safety. The JIT compiles each method on its first call; with tiered compilation (default since .NET Core 3.0), hot methods are recompiled with full optimization. Native AOT (.NET 7 and later) needs no JIT at all.

What is the difference between value types and reference types?

Fresher.nettypes

A value type variable holds the data and is copied on assignment; a reference type variable holds a reference to a heap object, and assignment copies the reference. Structs and enums are value types; classes, string, arrays and delegates are reference types.

Trap: value types do not always live on the stack. An int field of a class lives on the heap inside that object.

How does garbage collection work in .NET?

Senior.netmemory

The .NET GC is generational and tracing: it marks objects reachable from roots (locals, statics, GC handles), frees the rest and usually compacts the survivors. New objects start in Gen 0, collected often and cheaply; survivors move to Gen 1 and Gen 2, where collections are expensive. Objects of 85,000 bytes or more go to the large object heap, which is usually swept rather than compacted; see the large object heap docs.

What is IDisposable, and what does the using statement do?

Experienced.netmemory

IDisposable.Dispose() releases resources the GC does not manage, such as file handles and connections, at a known moment. A using block compiles to try and finally, so Dispose() runs even on an exception.

C#
using System;

class Connection : IDisposable
{
    private readonly string name;
    public Connection(string name) { this.name = name; Console.WriteLine("open " + name); }
    public void Dispose() { Console.WriteLine("close " + name); }
}

class Program
{
    static void Main()
    {
        try
        {
            using (var a = new Connection("A"))
            using (var b = new Connection("B"))
            {
                throw new Exception("query failed");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("caught: " + ex.Message);
        }
    }
}

Resources are disposed in reverse order, before the catch runs. See using statement.

What is the difference between a finalizer and Dispose, and what is the dispose pattern?

Senior.netmemory

Dispose is called by your code to release resources deterministically; a finalizer (~MyClass()) is called by the GC at some unknown later time, as a safety net.

class NativeBuffer : IDisposable
{
    private IntPtr handle;      // unmanaged memory
    private bool disposed;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);   // finalizer no longer needed
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposed) return;
        if (disposing) { /* dispose other IDisposable fields here */ }
        Marshal.FreeHGlobal(handle); // free unmanaged resources in both paths
        disposed = true;
    }

    ~NativeBuffer() { Dispose(false); }
}

Objects with a finalizer survive an extra GC, so add one only when the class owns an unmanaged handle directly. Modern code wraps the handle in a SafeHandle instead.

What is the difference between an assembly and a namespace?

Fresher.net

An assembly is a versioned unit of deployment, a .dll or .exe with IL and metadata; a namespace is a logical prefix for type names. One assembly can hold many namespaces and one namespace can span assemblies. Only the assembly affects access, through internal.

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

Fresher.net

.NET Framework is the original Windows-only runtime, stopped at 4.8.x, and its default C# version is 7.3 (see C# language versioning). .NET Core was the cross-platform, open-source rewrite, called .NET from version 5; it gets the latest C# and a yearly release, .NET 10 is the current long-term support version, and .NET 8 support ends in November 2026.

Why must you override GetHashCode when you override Equals?

Experienced.netcollections

Dictionary and HashSet use the hash code to find the bucket and only then call Equals, so equal objects with different hash codes are treated as different.

C#
using System;
using System.Collections.Generic;

class Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }

    public override bool Equals(object obj)
    {
        var p = obj as Point;
        return p != null && p.X == X && p.Y == Y;
    }
    // GetHashCode not overridden: each object keeps its own reference-based hash
}

class FixedPoint : Point
{
    public FixedPoint(int x, int y) : base(x, y) { }
    public override bool Equals(object obj) { return base.Equals(obj); }
    public override int GetHashCode() { return X * 31 + Y; }
}

class Program
{
    static void Main()
    {
        var broken = new HashSet<Point> { new Point(1, 2), new Point(1, 2) };
        var correct = new HashSet<Point> { new FixedPoint(1, 2), new FixedPoint(1, 2) };
        Console.WriteLine(broken.Count + " " + correct.Count);
    }
}

Equal objects must return equal hash codes. Write HashCode.Combine(X, Y), or use a record, which generates both methods.

What is the difference between a shallow copy and a deep copy in C#?

Experiencedtypesmemory

A shallow copy copies the fields as they are, so reference fields still point at the same objects; a deep copy also copies those objects. MemberwiseClone() is shallow.

C#
using System;
using System.Collections.Generic;

class Order
{
    public string Id;
    public List<string> Items = new List<string>();

    public Order ShallowCopy() { return (Order)MemberwiseClone(); }

    public Order DeepCopy()
    {
        var copy = (Order)MemberwiseClone();
        copy.Items = new List<string>(Items);   // copy the list too
        return copy;
    }
}

class Program
{
    static void Main()
    {
        var original = new Order { Id = "A1" };
        original.Items.Add("pen");

        var shallow = original.ShallowCopy();
        var deep = original.DeepCopy();
        original.Items.Add("book");

        Console.WriteLine(shallow.Items.Count + " " + deep.Items.Count);
        Console.WriteLine(ReferenceEquals(shallow.Items, original.Items));
    }
}

The shallow copy shares the list and sees the new item (2); the deep copy does not (1).

What is the difference between the stack and the heap in .NET?

Experienced.netmemory

The stack holds method frames (parameters, locals, return addresses), one stack per thread, freed when the method returns. The heap holds class instances, arrays and boxed values, is shared by all threads, and is freed by the GC. Deep recursion ends in StackOverflowException, which cannot be caught.

C# collections and LINQ interview questions

List, Dictionary, HashSet and the LINQ behavior that trips people up: deferred execution, yield, IQueryable.

What is the difference between an array, List<T> and ArrayList?

Freshercollections

An array has a fixed length; List<T> is a generic, resizable list backed by an array; ArrayList is the old non-generic version that boxes every value type.

C#
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        int[] arr = { 3, 1, 2 };
        Array.Sort(arr);
        Console.WriteLine(string.Join(",", arr) + " length " + arr.Length);

        var list = new List<int> { 3, 1, 2 };
        list.Add(5);
        list.Remove(1);
        Console.WriteLine(string.Join(",", list) + " count " + list.Count);
    }
}

List<T> doubles its array when full, so Add is O(1) amortized. See List.

What is the difference between IEnumerable<T>, ICollection<T> and IList<T>?

Experiencedcollections

They are layers: IEnumerable<T> can only be enumerated; ICollection<T> adds Count, Add, Remove and Contains; IList<T> adds access by index. Accept the smallest interface a method needs. Trap: an array implements IList<T>, but Add on it throws NotSupportedException.

How does Dictionary<TKey, TValue> work internally?

Experiencedcollectionshashing

Dictionary is a hash table: GetHashCode() on the key picks a bucket and Equals() finds the entry, so operations are O(1) on average and O(n) when many keys collide.

C#
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        string text = "the cat and the hat and the bat";
        var counts = new Dictionary<string, int>();

        foreach (var word in text.Split(' '))
        {
            int c;
            counts.TryGetValue(word, out c);   // c is 0 when the key is missing
            counts[word] = c + 1;
        }

        Console.WriteLine(counts["the"] + " " + counts["and"]);
        Console.WriteLine(counts.ContainsKey("dog"));
    }
}

Traps: dict[key] throws KeyNotFoundException for a missing key, and writes are not thread-safe. See the hash table visualization.

What is a HashSet<T>, and when would you use it?

Freshercollectionshashing

HashSet<T> stores unique values with O(1) average Add, Remove and Contains, and no order. Use it for "seen before" checks and set operations.

C#
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var seen = new HashSet<int>();
        int[] nums = { 4, 2, 7, 2, 9 };

        foreach (int n in nums)
        {
            if (!seen.Add(n))   // Add returns false when the value is already there
                Console.WriteLine("duplicate: " + n);
        }

        var a = new HashSet<int> { 1, 2, 3 };
        a.IntersectWith(new[] { 2, 3, 4 });
        Console.WriteLine(string.Join(",", a));
    }
}

Replacing a list's O(n) Contains with a HashSet inside a loop turns O(n²) into O(n).

What does this print: a LINQ Where counted after the list changes?

Experiencedlinq
C#
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var nums = new List<int> { 1, 2, 3 };
        var big = nums.Where(n => n > 1);
        nums.Add(4);
        Console.WriteLine(big.Count());
    }
}

Predict the output

Where builds a query that runs each time it is enumerated (deferred execution). Count() enumerates it after 4 was added, sees 2, 3, 4 and prints 3. Call ToList() to freeze a result; enumerating an expensive query twice does the work twice.

What is the difference between First, FirstOrDefault, Single and SingleOrDefault?

Fresherlinq

First returns the first match and throws if there is none; Single returns the only match and throws on zero or more than one. The OrDefault versions return default(T) when nothing matches, but SingleOrDefault still throws on two or more.

C#
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int[] nums = { 4, 8, 15, 16 };
        Console.WriteLine(nums.First(n => n > 5));
        Console.WriteLine(nums.FirstOrDefault(n => n > 100));
        try
        {
            Console.WriteLine(nums.Single(n => n > 5));
        }
        catch (InvalidOperationException)
        {
            Console.WriteLine("Single threw: more than one match");
        }
    }
}

Use Single when exactly one match is a rule, such as a lookup by primary key.

What does this print: when the body of a yield return method runs?

Experiencedlinqiterators
C#
using System;
using System.Collections.Generic;

class Program
{
    static IEnumerable<int> Gen()
    {
        Console.Write("start ");
        yield return 1;
        Console.Write("mid ");
        yield return 2;
    }

    static void Main()
    {
        var g = Gen();
        Console.Write("before ");
        foreach (var x in g)
            Console.Write(x + " ");
        Console.WriteLine();
    }
}

Predict the output

Calling an iterator such as Gen() runs none of its body. foreach starts it on the first MoveNext; it runs to each yield return and pauses, so before prints first, then start 1, then mid 2. See IEnumerable and yield.

What happens if you remove items from a List<T> inside a foreach loop?

Experiencedcollections

The enumerator detects the change and throws InvalidOperationException on the next step. Use RemoveAll, loop backwards with for, or iterate over a copy.

C#
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var nums = new List<int> { 1, 2, 3, 4, 5, 6 };
        try
        {
            foreach (var n in nums)
                if (n % 2 == 0) nums.Remove(n);
        }
        catch (InvalidOperationException e)
        {
            Console.WriteLine(e.GetType().Name);
        }

        nums = new List<int> { 1, 2, 3, 4, 5, 6 };
        nums.RemoveAll(n => n % 2 == 0);
        Console.WriteLine(string.Join(",", nums));

        nums = new List<int> { 1, 2, 3, 4, 5, 6 };
        for (int i = nums.Count - 1; i >= 0; i--)
            if (nums[i] % 2 == 0) nums.RemoveAt(i);
        Console.WriteLine(string.Join(",", nums));
    }
}

Backwards, because removing at i shifts later items left and a forward loop skips one.

How do you group items with LINQ? Show GroupBy with an example.

Experiencedlinq

GroupBy(keySelector) returns groups, each with a Key and the items that share it, like SQL's GROUP BY.

C#
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        string[] words = { "eat", "tea", "tan", "ate", "nat", "bat" };

        var groups = words
            .GroupBy(w => new string(w.OrderBy(c => c).ToArray()))
            .OrderByDescending(g => g.Count());

        foreach (var g in groups)
            Console.WriteLine(g.Key + ": " + string.Join(" ", g));
    }
}

Grouping by sorted letters solves group anagrams. See LINQ.

What is the difference between Select and SelectMany in LINQ?

Experiencedlinq

Select maps each item to one result, so a sequence of lists stays a sequence of lists. SelectMany maps each item to a sequence and flattens them all into one.

C#
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var teams = new Dictionary<string, string[]>
        {
            { "backend", new[] { "Ravi", "Meera" } },
            { "frontend", new[] { "Arjun" } }
        };

        var counts = teams.Select(t => t.Value.Length);
        Console.WriteLine(string.Join(",", counts));

        var everyone = teams.SelectMany(t => t.Value);
        Console.WriteLine(string.Join(",", everyone));
    }
}

What is the difference between IEnumerable<T> and IQueryable<T>?

Seniorlinq

IEnumerable<T> runs LINQ in memory with compiled delegates. IQueryable<T> builds an expression tree that a provider such as Entity Framework Core translates into SQL.

// IQueryable: the filter becomes WHERE in SQL, only matching rows come back
var active = db.Users.Where(u => u.IsActive).ToList();

// AsEnumerable switches to in-memory LINQ: every row is loaded, then filtered
var slow = db.Users.AsEnumerable().Where(u => u.IsActive).ToList();

Trap: AsEnumerable, or passing the query to an IEnumerable<T> parameter, silently moves filtering from the database into your app.

Which .NET collection would you choose for each job: List, Queue, Stack, LinkedList, SortedDictionary, ConcurrentDictionary?

Experiencedcollections

Choose by the operation you do most.

NeedCollection
indexed dataList<T>
first in, first outQueue<T>
last in, first outStack<T>
lookup by keyDictionary<K,V>
sorted keysSortedDictionary<K,V>
priority order (.NET 6 and later)PriorityQueue<T,P>
shared between threadsConcurrentDictionary<K,V>

LinkedList<T> is rarely faster than List<T>, whose contiguous array suits the CPU cache.

C# async and await interview questions

Tasks, threads, async and await, cancellation, deadlocks and thread safety.

What do async and await do in C#?

Fresherasync

async lets a method use await; await pauses the method until a Task completes without blocking the thread, and the rest runs later as a continuation.

C#
using System;
using System.Threading.Tasks;

class Program
{
    static async Task<int> DownloadSizeAsync(string url)
    {
        await Task.Delay(100);          // stands in for a network call
        return url.Length * 100;
    }

    static async Task RunAsync()
    {
        int size = await DownloadSizeAsync("coddy.tech");
        Console.WriteLine("size: " + size);
    }

    static void Main()
    {
        RunAsync().Wait();              // C# 7.1 and later allow async Task Main instead
    }
}

No thread is busy while the method waits on I/O, which is why async lets a server handle more requests. See async and await.

What does this print: code before and after the first await?

Experiencedasync
C#
using System;
using System.Threading.Tasks;

class Program
{
    static async Task Work()
    {
        Console.Write("A");
        await Task.Delay(50);
        Console.Write("C");
    }

    static void Main()
    {
        Task t = Work();
        Console.Write("B");
        t.Wait();
        Console.WriteLine();
    }
}

Predict the output

Work() runs synchronously until its first await on an unfinished task, so A prints inside the call. It then returns a pending Task; Main prints B and blocks on t.Wait(), and the continuation prints C. Code before the first await runs on the caller's thread.

What is the difference between a Task and a Thread?

Experiencedasyncthreads

A Thread is an OS thread you manage; a Task is an operation and its result, which may use no thread at all. Task.Run work runs on a thread pool thread, and I/O tasks hold no thread while they wait. Tasks also return values, rethrow exceptions on await, and compose with WhenAll.

What is the difference between Task.WhenAll, Task.WaitAll and Task.WhenAny?

Experiencedasync

WhenAll returns a task that completes when every task completes; WaitAll blocks the thread until they finish; WhenAny completes with the first task to finish.

C#
using System;
using System.Diagnostics;
using System.Threading.Tasks;

class Program
{
    static async Task<int> Fetch(int id, int ms)
    {
        await Task.Delay(ms);
        return id * 10;
    }

    static async Task RunAsync()
    {
        var sw = Stopwatch.StartNew();
        int[] results = await Task.WhenAll(Fetch(1, 300), Fetch(2, 300), Fetch(3, 300));
        Console.WriteLine(string.Join(",", results));
        Console.WriteLine(sw.ElapsedMilliseconds < 600 ? "ran concurrently" : "ran one by one");

        Task<int> first = await Task.WhenAny(Fetch(4, 400), Fetch(5, 50));
        Console.WriteLine("first result: " + first.Result);
    }

    static void Main() { RunAsync().Wait(); }
}

Three 300 ms calls take about 300 ms. Trap: awaiting WhenAll rethrows only the first exception, while the WhenAll task's Exception.InnerExceptions holds all of them.

Why should you avoid async void?

Experiencedasync

An async void method cannot be awaited, so the caller cannot tell when it finishes or catch its exceptions, which usually crash the process.

async void SaveBad()  { await db.SaveAsync(); }   // fire and forget, exceptions escape
async Task SaveGood() { await db.SaveAsync(); }   // caller can await and catch

try { SaveBad(); } catch { /* never runs for async exceptions */ }

Use it only for event handlers, with a try and catch inside. An async lambda passed as an Action is also async void.

What is ValueTask, and when should you use it instead of Task?

Seniorasyncperformance

ValueTask<T> is a struct that can carry a result directly, so a method that usually completes synchronously (a cache hit) avoids allocating a Task. Use it on such hot paths and Task elsewhere.

private readonly Dictionary<int, string> cache = new();

public ValueTask<string> GetNameAsync(int id)
{
    if (cache.TryGetValue(id, out var name))
        return new ValueTask<string>(name);                // no allocation
    return new ValueTask<string>(LoadAndCacheAsync(id));   // wraps a real Task
}

Await a ValueTask exactly once, and never read .Result before it completes.

Why can .Result or .Wait() on an async method deadlock, and what does ConfigureAwait(false) change?

Seniorasync

With a single-threaded synchronization context (WinForms, WPF, classic ASP.NET), .Result blocks the thread the awaited method needs to resume on, so each waits for the other.

// WPF button handler
var data = LoadAsync().Result;   // blocks the UI thread

async Task<string> LoadAsync()
{
    var s = await http.GetStringAsync(url);  // wants to resume on the UI thread
    return s;                                // never gets there
}

Await all the way. In library code, ConfigureAwait(false) resumes on the thread pool instead of the captured context. ASP.NET Core has no synchronization context, so it does not deadlock this way.

How do you cancel an async operation in C#?

Seniorasync

Pass a CancellationToken down the call chain; the caller cancels through its CancellationTokenSource, and the code passes the token on or calls ThrowIfCancellationRequested().

C#
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task<int> SlowWork(CancellationToken token)
    {
        int steps = 0;
        for (int i = 0; i < 100; i++)
        {
            token.ThrowIfCancellationRequested();
            await Task.Delay(20, token);
            steps++;
        }
        return steps;
    }

    static async Task RunAsync()
    {
        var cts = new CancellationTokenSource();
        cts.CancelAfter(100);
        try
        {
            await SlowWork(cts.Token);
            Console.WriteLine("finished");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("canceled");
        }
    }

    static void Main() { RunAsync().Wait(); }
}

Cancellation is cooperative: code that never checks the token cannot be stopped.

What does the lock statement do, and when do you use Interlocked instead?

Seniorthreads

lock (obj) lets one thread at a time run the block for that object, through Monitor.Enter and Monitor.Exit in a try and finally (on a C# 13 System.Threading.Lock, through EnterScope(); see the lock statement docs). For a single counter, Interlocked.Increment is a single atomic operation.

C#
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static int unsafeCount, lockedCount, atomicCount;
    static readonly object gate = new object();

    static void Main()
    {
        Parallel.For(0, 100000, i =>
        {
            unsafeCount++;                          // race: read, add, write
            lock (gate) { lockedCount++; }
            Interlocked.Increment(ref atomicCount);
        });

        Console.WriteLine("locked: " + lockedCount);
        Console.WriteLine("atomic: " + atomicCount);
        Console.WriteLine("unsafe is exact: " + (unsafeCount == 100000));
    }
}

The unsynchronized counter usually loses updates, so its line can print False or True. You cannot await inside a lock; use SemaphoreSlim.WaitAsync. See lock.

Modern C# features interview questions (C# 8 to 12)

Records, patterns, nullable references, init and Span<T>; they need a modern SDK, so output is in comments.

What is a record in C#, and how is it different from a class?

Experiencedmodern c#

A record (C# 9) is a class with generated value-based equality: two records are equal when the runtime type and all fields match. It also gets a readable ToString(), deconstruction and with copies.

public record Person(string Name, int Age);   // positional record, init-only properties

var a = new Person("Asha", 30);
var b = new Person("Asha", 30);
Console.WriteLine(a == b);          // True  (a class would print False)
Console.WriteLine(a);               // Person { Name = Asha, Age = 30 }

var older = a with { Age = 31 };    // copy with one property changed
var (name, age) = older;            // deconstruction

record struct (C# 10) is the value-type version. See records.

What are switch expressions and pattern matching in modern C#?

Experiencedmodern c#

A switch expression (C# 8) is a switch that returns a value, with patterns in its arms: type, property, relational and and/or/not patterns (C# 9), and list patterns (C# 11).

static string Classify(object o) => o switch
{
    null                    => "null",
    int n when n < 0        => "negative int",
    int and (0 or 1)        => "bit",
    int                     => "int",
    string { Length: 0 }    => "empty string",
    string s                => $"string of {s.Length}",
    _                       => "something else"
};

int[] nums = [1, 2, 3];
if (nums is [1, .., var last])          // list pattern, C# 11
    Console.WriteLine(last);            // 3

Arms are tried top to bottom, so put specific patterns first. See switch expressions.

What are nullable reference types in C# 8?

Seniormodern c#

With <Nullable>enable</Nullable>, string means "never null" and string? means "may be null", and the compiler warns on possible null dereferences.

#nullable enable
string name = null;          // warning CS8625: null literal to non-nullable type
string? nick = null;         // fine

int len = nick.Length;       // warning CS8602: dereference of a possibly null reference
int ok = nick?.Length ?? 0;  // fine

string forced = nick!;       // ! tells the compiler "trust me", no runtime check

It is compile-time only: at run time a string can still be null, for example after deserialization.

What do init and required do on properties?

Experiencedmodern c#

init (C# 9) makes a property settable only during object creation; required (C# 11) forces callers to set it in the object initializer.

public class Order
{
    public required string Id { get; init; }
    public decimal Total { get; init; }
}

var o = new Order { Id = "A-17", Total = 99m };  // fine
o.Total = 10m;                                   // error: init-only property
var bad = new Order { Total = 5m };              // error CS9035: Id is required

Together they give immutable objects without a long constructor.

What are top-level statements, global usings and file-scoped namespaces?

Freshermodern c#

Top-level statements (C# 9) let Program.cs hold code without a class Program and Main; global usings (C# 10) import a namespace for the whole project; file-scoped namespaces (C# 10) remove one level of braces.

// Program.cs, the whole file (top-level statements)
Console.WriteLine("Hello");
return 0;

// GlobalUsings.cs
global using System.Text.Json;

// Customer.cs, file-scoped namespace
namespace Shop.Models;

public class Customer { }

Only one file per project can have top-level statements.

What are primary constructors and collection expressions in C# 12?

Experiencedmodern c#

C# 12 lets a class or struct declare constructor parameters next to its name (primary constructors) and build collections with brackets and the .. spread (collection expressions).

public class OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
    public Order Get(int id)
    {
        log.LogInformation("Loading {Id}", id);
        return repo.Find(id);           // parameters are in scope everywhere in the class
    }
}

int[] a = [1, 2, 3];
List<int> b = [0, .. a, 4];             // 0, 1, 2, 3, 4
Span<char> empty = [];

Trap: in a class, those parameters do not become public properties as in a record; they are mutable captured variables.

What are Span<T> and Memory<T>, and why do they matter for performance?

Seniormodern c#performance

Span<T> (C# 7.2) is a view over contiguous memory (an array, part of a string, stack memory) that slices without copying. It is a ref struct that lives only on the stack; Memory<T> can be stored in fields and used across await.

ReadOnlySpan<char> line = "2026-10-09,ORDER,149.50";
int comma = line.IndexOf(',');
ReadOnlySpan<char> date = line.Slice(0, comma);   // no new string allocated
decimal total = decimal.Parse(line.Slice(line.LastIndexOf(',') + 1));

Span<byte> buffer = stackalloc byte[256];         // stack memory, no GC

A span cannot be a field of a class, be boxed, or be captured by a lambda.

What are default interface methods (C# 8)?

Seniormodern c#interfaces

Since C# 8 (.NET Core 3.0 and later), an interface member can have a body that implementers get unless they write their own, so a library can add a member without breaking them.

public interface ILogger
{
    void Log(string message);
    void LogError(string message) => Log("ERROR: " + message);   // default body
}

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

ILogger log = new ConsoleLogger();
log.LogError("disk full");        // works through the interface
// new ConsoleLogger().LogError(...)  does not compile: not a class member

The default member is reachable only through the interface type, and it does not work on .NET Framework.

C# interview questions for experienced (5 and 10 years)

Delegates, generics, variance, dependency injection, memory leaks, LINQ performance and the ASP.NET Core pipeline.

What is the difference between a delegate and an event in C#?

Experienceddelegates

A delegate is a type-safe reference to one or more methods (Func<int, int>, Action<string>). An event wraps a delegate so outside code can only += and -=; only the declaring class can raise it.

C#
using System;

class Thermostat
{
    public event Action<int> TooHot;     // outside code can only += or -=

    public void Report(int temp)
    {
        if (temp > 30 && TooHot != null)
            TooHot(temp);                  // only this class can raise it
    }
}

class Program
{
    static void Main()
    {
        Func<int, int, int> add = (a, b) => a + b;
        Console.WriteLine(add(2, 3));

        var t = new Thermostat();
        t.TooHot += temp => Console.WriteLine("alert: " + temp);
        t.TooHot += temp => Console.WriteLine("logged: " + temp);
        t.Report(25);
        t.Report(35);
        // t.TooHot = null;  does not compile outside Thermostat
    }
}

A publisher keeps every subscriber alive until it unsubscribes, a common memory leak. See delegates.

What are generic constraints in C#? Give an example.

Experiencedgenerics

A constraint (where T : ...) limits which types T can be, so generic code can use members those types guarantee: class, struct, new(), a base class or an interface.

C#
using System;
using System.Collections.Generic;

class Program
{
    static T Max<T>(IEnumerable<T> items) where T : IComparable<T>
    {
        bool first = true;
        T best = default(T);
        foreach (var item in items)
        {
            if (first || item.CompareTo(best) > 0) best = item;
            first = false;
        }
        return best;
    }

    static T Create<T>() where T : new() { return new T(); }

    static void Main()
    {
        Console.WriteLine(Max(new[] { 3, 9, 4 }));
        Console.WriteLine(Max(new[] { "pear", "apple", "zucchini" }));
        Console.WriteLine(Create<List<int>>().Count);
    }
}

.NET generics are reified, unlike Java's: List<int> is a real runtime type with no boxing. See generics.

What are covariance and contravariance in C# generics?

Seniorgenerics

Covariance (out T) lets an IEnumerable<string> be used as an IEnumerable<object>; contravariance (in T) lets an Action<object> be used as an Action<string>.

C#
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        IEnumerable<string> names = new List<string> { "a", "b" };
        IEnumerable<object> objects = names;          // covariance: out T
        foreach (var o in objects) Console.Write(o + " ");
        Console.WriteLine();

        Action<object> printAny = x => Console.WriteLine("got " + x);
        Action<string> printString = printAny;        // contravariance: in T
        printString("text");

        // List<object> list = new List<string>();    does not compile: List<T> is invariant
    }
}

List<T> is invariant because it both reads and writes T. Variance works only on interfaces and delegates, and only for reference types.

What is the difference between throw; and throw ex;?

Experiencedexceptions

throw; rethrows the caught exception with its original stack trace; throw ex; resets the trace to the current line, so logs lose where the error happened.

C#
using System;
using System.Runtime.CompilerServices;

class Program
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    static void Inner() { throw new InvalidOperationException("boom"); }

    static void RethrowEx()
    {
        try { Inner(); }
        catch (Exception ex) { throw ex; }
    }

    static void Rethrow()
    {
        try { Inner(); }
        catch (Exception) { throw; }
    }

    static void Main()
    {
        try { RethrowEx(); }
        catch (Exception ex) { Console.WriteLine("throw ex keeps Inner: " + ex.StackTrace.Contains("Inner")); }

        try { Rethrow(); }
        catch (Exception ex) { Console.WriteLine("throw keeps Inner:    " + ex.StackTrace.Contains("Inner")); }
    }
}

To add context, wrap it: throw new OrderException("Save failed", ex) keeps the original as InnerException.

What does this print: lambdas capturing a for variable and a foreach variable?

Seniordelegatesclosures
C#
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var actions = new List<Action>();
        for (int i = 0; i < 3; i++)
            actions.Add(() => Console.Write(i));
        foreach (var j in new[] { 0, 1, 2 })
            actions.Add(() => Console.Write(j));

        foreach (var a in actions) a();
        Console.WriteLine();
    }
}

Predict the output

A lambda captures the variable, not its value. A for loop has one i, which is 3 when the lambdas run: 333. Since C# 5, foreach declares a fresh variable per iteration: 012. Fix the for loop with a copy in the body: int copy = i;.

What are the dependency injection lifetimes in ASP.NET Core, and what is a captive dependency?

Seniorarchitecture

ASP.NET Core has three lifetimes: Transient (a new instance every resolve), Scoped (one per HTTP request) and Singleton (one for the app).

builder.Services.AddTransient<IEmailFormatter, EmailFormatter>(); // stateless, cheap
builder.Services.AddScoped<AppDbContext>();                       // one per request
builder.Services.AddSingleton<IClock, SystemClock>();             // shared, thread-safe

// Captive dependency: a singleton holding a scoped service
public class ReportCache(AppDbContext db) { }                     // registered as singleton: bug

A captive dependency is a longer-lived service holding a shorter-lived one, such as a singleton keeping one scoped DbContext forever, across requests and threads. A singleton that needs scoped work creates scopes with IServiceScopeFactory.

How do you implement a thread-safe singleton in C#?

Seniordesign patternsthreads

The simplest is a static readonly field, because the CLR runs static initialization once even with many threads. For laziness, use Lazy<T>, which is thread-safe by default.

C#
using System;
using System.Threading.Tasks;

sealed class Config
{
    private static readonly Lazy<Config> instance = new Lazy<Config>(() => new Config());
    public static Config Instance { get { return instance.Value; } }

    public static int Created;
    private Config() { Created++; }
}

class Program
{
    static void Main()
    {
        Parallel.For(0, 1000, i => { var c = Config.Instance; });
        Console.WriteLine("instances created: " + Config.Created);
        Console.WriteLine(ReferenceEquals(Config.Instance, Config.Instance));
    }
}

In ASP.NET Core, register the class with AddSingleton instead, so tests can replace it.

What are common LINQ performance mistakes you look for in code review?

Seniorlinqperformance

Enumerating a deferred query more than once (call ToList() once), Count() > 0 instead of Any(), and queries inside loops: Where(...).First() per item (build a Dictionary once) or lazy loading per row in Entity Framework, the N+1 problem (use Include or a projection). With EF, filter before ToList() so SQL does the work.

What are attributes and reflection in C#, and when are they used?

Seniorreflection

An attribute is metadata attached to code ([Obsolete], [HttpGet]); reflection reads types and attributes at run time. Frameworks use both to find routes, serializable properties and test methods.

C#
using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Property)]
class ColumnAttribute : Attribute
{
    public string Name { get; private set; }
    public ColumnAttribute(string name) { Name = name; }
}

class User
{
    [Column("user_name")] public string Name { get; set; }
    [Column("user_age")] public int Age { get; set; }
}

class Program
{
    static void Main()
    {
        foreach (PropertyInfo p in typeof(User).GetProperties())
        {
            var col = (ColumnAttribute)Attribute.GetCustomAttribute(p, typeof(ColumnAttribute));
            Console.WriteLine(p.Name + " -> " + col.Name);
        }
    }
}

Reflection is slow and works badly with trimming and Native AOT, so modern libraries use source generators. See reflection and attributes.

How do you design exception handling in a large C# application?

Seniorexceptionsarchitecture

Catch only where you can do something useful, and let the rest reach one global handler that logs and returns a clean error. Use TryParse-style methods for expected failures, catch specific types, wrap to add context, and filter with catch ... when.

C#
using System;

class InsufficientFundsException : Exception
{
    public decimal Shortfall { get; private set; }
    public InsufficientFundsException(decimal shortfall)
        : base("Short by " + shortfall) { Shortfall = shortfall; }
}

class Program
{
    static void Withdraw(decimal balance, decimal amount)
    {
        if (amount > balance) throw new InsufficientFundsException(amount - balance);
    }

    static void Main()
    {
        try
        {
            Withdraw(100m, 130m);
        }
        catch (InsufficientFundsException ex) when (ex.Shortfall < 50m)
        {
            Console.WriteLine("Offer overdraft: " + ex.Message);
        }
    }
}

In ASP.NET Core, an IExceptionHandler (.NET 8) maps exceptions to ProblemDetails responses.

How does the ASP.NET Core middleware pipeline work?

Seniorarchitecture

Each request passes through an ordered chain of middleware; each component can act on the request, call next(), and act on the response on the way back. Skipping next() short-circuits the pipeline.

var app = builder.Build();

app.UseExceptionHandler("/error");   // outermost: catches what later steps throw

app.Use(async (context, next) =>     // custom inline middleware
{
    var sw = Stopwatch.StartNew();
    await next();                    // the rest of the pipeline runs here
    app.Logger.LogInformation("{Path} took {Ms} ms", context.Request.Path, sw.ElapsedMilliseconds);
});

app.UseHttpsRedirection();
app.UseStaticFiles();                // may short-circuit for files
app.UseRouting();
app.UseAuthentication();             // who are you
app.UseAuthorization();              // are you allowed
app.MapControllers();

Order matters: the exception handler goes first, and authentication before authorization.

C# coding interview questions

Classic coding questions solved in idiomatic C#, each linked to a problem you can submit on the Coddy judge.

How do you reverse a string in C# without using Array.Reverse or LINQ?

Freshercodingstrings

Copy the characters into a char[], swap from both ends toward the middle, and build a new string: O(n) time and space.

C#
using System;

class Program
{
    static string Reverse(string s)
    {
        char[] c = s.ToCharArray();
        for (int i = 0, j = c.Length - 1; i < j; i++, j--)
        {
            char tmp = c[i];
            c[i] = c[j];
            c[j] = tmp;
        }
        return new string(c);
    }

    static void Main()
    {
        Console.WriteLine(Reverse("interview"));
        Console.WriteLine(Reverse("a"));
        Console.WriteLine(Reverse("") == "");
    }
}

A char-by-char reverse breaks emoji and combining accents; reverse StringInfo text elements for those.

Write a C# method that checks whether a sentence is a palindrome, ignoring case and punctuation.

Freshercodingstrings

Use two pointers from both ends, skip characters that fail char.IsLetterOrDigit, and compare with char.ToLowerInvariant: O(n) time, O(1) extra space.

C#
using System;

class Program
{
    static bool IsPalindrome(string s)
    {
        int i = 0, j = s.Length - 1;
        while (i < j)
        {
            if (!char.IsLetterOrDigit(s[i])) { i++; continue; }
            if (!char.IsLetterOrDigit(s[j])) { j--; continue; }
            if (char.ToLowerInvariant(s[i]) != char.ToLowerInvariant(s[j])) return false;
            i++;
            j--;
        }
        return true;
    }

    static void Main()
    {
        Console.WriteLine(IsPalindrome("A man, a plan, a canal: Panama"));
        Console.WriteLine(IsPalindrome("race a car"));
    }
}

Plain ToLower() depends on the current culture (the Turkish I problem).

How do you count how many times each character appears in a string in C#?

Freshercodingcollections

Count into a Dictionary<char, int> in one O(n) pass, or use GroupBy(c => c). The dictionary also gives the first non-repeating character.

C#
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        string s = "programming";
        var counts = new Dictionary<char, int>();
        foreach (char c in s)
            counts[c] = counts.ContainsKey(c) ? counts[c] + 1 : 1;

        foreach (var kv in counts.Where(kv => kv.Value > 1))
            Console.Write(kv.Key + "=" + kv.Value + " ");
        Console.WriteLine();

        char firstUnique = s.First(c => counts[c] == 1);
        Console.WriteLine("first unique: " + firstUnique);
    }
}

For lowercase ASCII only, an int[26] indexed by c - 'a' is faster.

How do you find duplicate values in an array in C#?

Freshercodinghashing

Add each value to a HashSet<int>; when Add returns false, the value is a duplicate, in O(n) overall. With LINQ, GroupBy(x => x).Where(g => g.Count() > 1) does it in one expression.

C#
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        int[] nums = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };

        var seen = new HashSet<int>();
        var dups = new HashSet<int>();
        foreach (int n in nums)
            if (!seen.Add(n)) dups.Add(n);
        Console.WriteLine(string.Join(",", dups));

        var withCounts = nums.GroupBy(n => n)
                             .Where(g => g.Count() > 1)
                             .Select(g => g.Key + "x" + g.Count());
        Console.WriteLine(string.Join(" ", withCounts));
    }
}

Without extra memory, sort first and compare neighbors.

Solve Two Sum in C#: return the indexes of the two numbers that add up to a target.

Experiencedcodinghashing

Walk the array once with a Dictionary<int, int> from value to index, checking whether target - number is already in it: O(n) time and space.

C#
using System;
using System.Collections.Generic;

class Program
{
    static int[] TwoSum(int[] nums, int target)
    {
        var index = new Dictionary<int, int>();
        for (int i = 0; i < nums.Length; i++)
        {
            int need = target - nums[i];
            int j;
            if (index.TryGetValue(need, out j))
                return new[] { j, i };
            index[nums[i]] = i;
        }
        return new int[0];
    }

    static void Main()
    {
        Console.WriteLine(string.Join(",", TwoSum(new[] { 2, 7, 11, 15 }, 9)));
        Console.WriteLine(string.Join(",", TwoSum(new[] { 3, 3 }, 6)));
    }
}

Look up before inserting the current number, so [3, 3] with target 6 works and no number pairs with itself.

How do you check if a string of brackets is balanced in C#?

Experiencedcodingcollections

Push each opening bracket onto a Stack<char>; on a closing bracket, the popped top must be its opener, and the stack must be empty at the end. O(n) time.

C#
using System;
using System.Collections.Generic;

class Program
{
    static bool IsValid(string s)
    {
        var stack = new Stack<char>();
        var pairs = new Dictionary<char, char> { { ')', '(' }, { ']', '[' }, { '}', '{' } };
        foreach (char c in s)
        {
            if (c == '(' || c == '[' || c == '{')
                stack.Push(c);
            else if (stack.Count == 0 || stack.Pop() != pairs[c])
                return false;
        }
        return stack.Count == 0;
    }

    static void Main()
    {
        Console.WriteLine(IsValid("{[()]}()"));
        Console.WriteLine(IsValid("([)]"));
        Console.WriteLine(IsValid("(("));
    }
}

Most failed attempts miss a closer on an empty stack or openers left at the end.

How do you reverse a singly linked list in C#?

Experiencedcodinglinked lists

Walk the list once with prev, current and next, pointing each node back at the previous one: O(n) time, O(1) extra space.

C#
using System;

class Node
{
    public int Value;
    public Node Next;
    public Node(int value, Node next) { Value = value; Next = next; }
}

class Program
{
    static Node Reverse(Node head)
    {
        Node prev = null, current = head;
        while (current != null)
        {
            Node next = current.Next;
            current.Next = prev;
            prev = current;
            current = next;
        }
        return prev;
    }

    static void Print(Node n)
    {
        for (; n != null; n = n.Next) Console.Write(n.Value + (n.Next != null ? " -> " : ""));
        Console.WriteLine();
    }

    static void Main()
    {
        var list = new Node(1, new Node(2, new Node(3, new Node(4, null))));
        Print(list);
        Print(Reverse(list));
    }
}

The recursive version uses O(n) stack. Watch it in the linked list visualization.

Design an LRU cache in C# with O(1) get and put.

Seniorcodingdesign

Combine a Dictionary from key to LinkedListNode for O(1) lookup with a LinkedList in recency order: move a node to the front on access, and evict from the back when full.

C#
using System;
using System.Collections.Generic;

class LruCache
{
    private readonly int capacity;
    private readonly Dictionary<int, LinkedListNode<KeyValuePair<int, string>>> map =
        new Dictionary<int, LinkedListNode<KeyValuePair<int, string>>>();
    private readonly LinkedList<KeyValuePair<int, string>> order =
        new LinkedList<KeyValuePair<int, string>>();

    public LruCache(int capacity) { this.capacity = capacity; }

    public string Get(int key)
    {
        LinkedListNode<KeyValuePair<int, string>> node;
        if (!map.TryGetValue(key, out node)) return null;
        order.Remove(node);
        order.AddFirst(node);              // most recently used at the front
        return node.Value.Value;
    }

    public void Put(int key, string value)
    {
        LinkedListNode<KeyValuePair<int, string>> node;
        if (map.TryGetValue(key, out node)) order.Remove(node);
        else if (map.Count == capacity)
        {
            map.Remove(order.Last.Value.Key);  // evict least recently used
            order.RemoveLast();
        }
        map[key] = order.AddFirst(new KeyValuePair<int, string>(key, value));
    }
}

class Program
{
    static void Main()
    {
        var cache = new LruCache(2);
        cache.Put(1, "one");
        cache.Put(2, "two");
        cache.Get(1);                      // 1 is now most recent
        cache.Put(3, "three");             // evicts 2
        Console.WriteLine(cache.Get(2) ?? "miss");
        Console.WriteLine(cache.Get(1) + " " + cache.Get(3));
    }
}

For thread safety, lock both methods, because the map and the list must change together.

Write FizzBuzz in C#.

Freshercoding

Print 1 to n with Fizz for multiples of 3, Buzz for multiples of 5 and FizzBuzz for both. Check 15 first, or multiples of 15 print Fizz.

C#
using System;

class Program
{
    static void Main()
    {
        for (int i = 1; i <= 15; i++)
        {
            string s = i % 15 == 0 ? "FizzBuzz"
                     : i % 3 == 0 ? "Fizz"
                     : i % 5 == 0 ? "Buzz"
                     : i.ToString();
            Console.Write(s + (i < 15 ? " " : "\n"));
        }
    }
}

Watch the bound: <= n, not < n.

Preparing for the interview

How do I prepare for a C# interview?
Cover C# basics (types, strings, OOP keywords), .NET runtime topics (value and reference types, GC, IDisposable), collections and LINQ, then async and await. Practice writing small programs by hand, since most rounds include a coding question such as Two Sum. Predict each snippet's output before you run it.
What C# topics are asked to freshers?
Data types, value vs reference types, string immutability, ref and out, access modifiers, virtual and override, abstract classes vs interfaces, exception handling, and simple coding questions such as FizzBuzz. Expect at least one predict-the-output question.
What is asked in a C# interview for 5 or 10 years of experience?
Design and runtime behavior: async internals and deadlocks, thread safety, garbage collection and memory leaks, LINQ and Entity Framework performance, dependency injection lifetimes, the ASP.NET Core pipeline and modern C#. At 10 years, expect system design and how you would structure a large codebase.
Do I need data structures and algorithms for a C# developer interview?
Yes, the basics. Know Dictionary, HashSet, Stack, Queue and List and the cost of their operations; arrays, strings, hashing, two pointers and simple linked lists cover most fresher and mid-level coding rounds.
Which C# version should I know for interviews?
Know C# 7 (tuples, out var, is patterns), common in existing code, and the features up to C# 12 (.NET 8): records, switch expressions, nullable reference types, init and required, primary constructors. Naming a C# 13 or 14 addition, such as the Lock type or the field keyword, shows you keep up.
Coddy programming languages illustration

Learn to code with Coddy

GET STARTED