C# Interview Questions with Answers
Most Asked C# Interview Questions for Software Engineer Roles
Introduction
This page provides a comprehensive collection of C# Interview Questions and Answers to help students, fresh graduates, and experienced .NET developers prepare for technical interviews. The questions range from basic C# programming concepts to advanced .NET application development topics frequently asked by top software companies. C# is one of the most popular object-oriented programming languages developed by Microsoft and is widely used for building desktop applications, web applications, enterprise software, cloud services, APIs, mobile apps, and games using the .NET platform. This interview guide includes beginner, intermediate, advanced, and scenario-based C# interview questions covering OOP concepts, collections, delegates, events, LINQ, asynchronous programming, multithreading, exception handling, memory management, ASP.NET Core, Entity Framework, and modern C# language features.
Why C#?
- Cross-platform development with .NET Core / .NET 5+
- Rich ecosystem with powerful libraries and frameworks (ASP.NET Core, Entity Framework, etc.)
- Automatic memory management with garbage collection
- Enterprise-grade, widely used in desktop, web, cloud, and game development
- Massive community support and highly in-demand for technical interviews
Most Asked C# Interview Questions
C# is a modern, object-oriented programming language developed by Microsoft as part of the .NET platform. It combines the power of C++ with the simplicity of Java and adds modern features like LINQ and async/await.
- Object-Oriented: Classes, inheritance, polymorphism, encapsulation
- Type-Safe: Strong typing with type inference via
var - LINQ: Language Integrated Query for data manipulation
- Async/Await: Simplified asynchronous programming
- Garbage Collection: Automatic memory management
// Hello World in C#
using System;
class Program {
static void Main() {
Console.WriteLine("Hello, World!");
}
}C# provides both value types (structs) and reference types (classes). The language includes built-in types and supports user-defined types.
int— 32-bit integerfloat— 32-bit floating point (must usefsuffix)double— 64-bit floating pointdecimal— 128-bit precision for financial calculationschar— 16-bit Unicode characterbool— true/falsestring— immutable sequence of characters
// Data Types in C#
using System;
class Program {
static void Main() {
int age = 25;
float salary = 50000.50f;
double pi = 3.14159265358979;
char grade = 'A';
bool isActive = true;
string name = "Alice";
decimal price = 99.99m;
Console.WriteLine($"Age: {age}");
Console.WriteLine($"Salary: {salary}");
Console.WriteLine($"Pi: {pi}");
Console.WriteLine($"Grade: {grade}");
Console.WriteLine($"Active: {isActive}");
Console.WriteLine($"Name: {name}");
Console.WriteLine($"Price: {price}");
}
}C# uses const for compile-time constants, readonly for runtime constants that can be set in the constructor, and var for type inference.
const— compile-time constant, must be initializedreadonly— runtime constant, can be set in constructorvar— compiler infers type from initializationdynamic— bypasses compile-time type checking
// Variables, Constants, and Readonly
using System;
class Program {
const double PI = 3.14159;
readonly int MAX = 100;
static void Main() {
int x = 10;
const int MIN_VALUE = 0;
// var type inference
var val = 3.14;
var str = "Hello";
Console.WriteLine($"x = {x}");
Console.WriteLine($"PI = {PI}");
Console.WriteLine($"val = {val}");
Console.WriteLine($"str = {str}");
}
}A class is a blueprint that encapsulates data and behavior. C# uses properties for clean access control, constructors for initialization, and finalizers for cleanup.
private— accessible only within the classpublic— accessible from anywhereprotected— accessible in derived classes- Properties provide getters and setters
- Finalizer
~ClassName()(called by garbage collector)
// OOP - Classes and Objects
using System;
class Car {
private string brand;
private int year;
private double price;
// Constructor
public Car(string brand, int year, double price) {
this.brand = brand;
this.year = year;
this.price = price;
}
// Properties
public string Brand { get { return brand; } }
public int Year { get { return year; } }
public double Price { get { return price; } }
// Method
public void Display() {
Console.WriteLine("Brand: " + brand + ", Year: " + year + ", Price: $" + price);
}
// Destructor (Finalizer)
~Car() {
Console.WriteLine(brand + " destroyed.");
}
}
class Program {
static void Main() {
Car c1 = new Car("Toyota", 2022, 25000.0);
Car c2 = new Car("BMW", 2023, 55000.0);
c1.Display();
c2.Display();
Console.WriteLine("Brand: " + c1.Brand);
}
}A constructor initializes an object when it is created. C# supports default, parameterized, and copy constructors via the this keyword. Finalizers (destructors) are called by the garbage collector.
- Default constructor — no parameters
- Parameterized constructor — accepts arguments
- Constructor chaining with
thiskeyword - Finalizer syntax:
~ClassName() - Use
IDisposablefor deterministic cleanup
// Constructors and Destructors
using System;
class Student {
private string name;
private int age;
// Default constructor
public Student() : this("Unknown", 0) {
Console.WriteLine("Default constructor called");
}
// Parameterized constructor
public Student(string name, int age) {
this.name = name;
this.age = age;
Console.WriteLine($"Parameterized constructor: {name}");
}
// Copy constructor
public Student(Student other) {
this.name = other.name;
this.age = other.age;
Console.WriteLine($"Copy constructor: {name}");
}
public void Display() {
Console.WriteLine($"Name: {name}, Age: {age}");
}
~Student() {
Console.WriteLine($"Destructor: {name}");
}
}
class Program {
static void Main() {
Student s1 = new Student();
Student s2 = new Student("Alice", 20);
Student s3 = new Student(s2); // copy constructor
s1.Display();
s2.Display();
s3.Display();
}
}Inheritance in C# uses the : syntax and supports single inheritance (one base class) plus multiple interface implementation.
sealed— prevents further inheritanceabstract— cannot be instantiatedvirtual— allows overriding in derived classesoverride— overrides a virtual methodbase— calls base class constructor/method
// Inheritance in C#
using System;
class Animal {
protected string name;
protected int age;
public Animal(string name, int age) {
this.name = name;
this.age = age;
}
public virtual void Speak() {
Console.WriteLine($"{name} makes a sound.");
}
public void Info() {
Console.WriteLine($"Name: {name}, Age: {age}");
}
}
class Dog : Animal {
private string breed;
public Dog(string name, int age, string breed) : base(name, age) {
this.breed = breed;
}
public override void Speak() {
Console.WriteLine($"{name} says: Woof!");
}
public void Display() {
Info();
Console.WriteLine($"Breed: {breed}");
}
}
class Cat : Animal {
public Cat(string name, int age) : base(name, age) { }
public override void Speak() {
Console.WriteLine($"{name} says: Meow!");
}
}
class Program {
static void Main() {
Dog dog = new Dog("Rex", 3, "German Shepherd");
Cat cat = new Cat("Whiskers", 2);
dog.Display();
dog.Speak();
cat.Speak();
// Polymorphism via base reference
Animal a = dog;
a.Speak();
}
}Polymorphism allows objects of different classes to be treated through a common base class or interface. Abstract classes define contracts that derived classes must implement.
abstractclass — cannot be instantiatedabstractmethod — no implementation, must be overriddenvirtual+override— runtime polymorphism- Interface — pure contract with no implementation
// Polymorphism and Abstract Classes
using System;
abstract class Shape {
public abstract double Area();
public abstract double Perimeter();
public void Display() {
Console.WriteLine($"Area: {Area()}, Perimeter: {Perimeter()}");
}
}
class Circle : Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public override double Area() {
return Math.PI * radius * radius;
}
public override double Perimeter() {
return 2 * Math.PI * radius;
}
}
class Rectangle : Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public override double Area() {
return width * height;
}
public override double Perimeter() {
return 2 * (width + height);
}
}
class Program {
static void Main() {
Shape[] shapes = {
new Circle(5.0),
new Rectangle(4.0, 6.0)
};
foreach (var s in shapes) {
s.Display();
}
}
}Operator overloading allows custom behavior for operators (+, -, ==, etc.) on user-defined types, making them more intuitive to use.
- Must be
public staticmethods - Operators:
+,-,*,/,==,!=,<,> - Unary operators:
++,--,! - Override
Equals()andGetHashCode()when overloading==
// Operator Overloading
using System;
class Vector2D {
public double X { get; set; }
public double Y { get; set; }
public Vector2D(double x = 0, double y = 0) {
X = x; Y = y;
}
// + operator
public static Vector2D operator +(Vector2D a, Vector2D b) {
return new Vector2D(a.X + b.X, a.Y + b.Y);
}
// - operator
public static Vector2D operator -(Vector2D a, Vector2D b) {
return new Vector2D(a.X - b.X, a.Y - b.Y);
}
// * scalar
public static Vector2D operator *(Vector2D v, double s) {
return new Vector2D(v.X * s, v.Y * s);
}
// == operator
public static bool operator ==(Vector2D a, Vector2D b) {
if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
return ReferenceEquals(a, b);
return a.X == b.X && a.Y == b.Y;
}
public static bool operator !=(Vector2D a, Vector2D b) {
return !(a == b);
}
public override string ToString() {
return $"({X}, {Y})";
}
}
class Program {
static void Main() {
Vector2D v1 = new Vector2D(3, 4);
Vector2D v2 = new Vector2D(1, 2);
Console.WriteLine($"v1 = {v1}");
Console.WriteLine($"v2 = {v2}");
Console.WriteLine($"v1 + v2 = {v1 + v2}");
Console.WriteLine($"v1 - v2 = {v1 - v2}");
Console.WriteLine($"v1 * 2 = {v1 * 2}");
Console.WriteLine($"v1 == v2: {v1 == v2}");
}
}Generics enable type-safe code that works with any type without boxing or casting. They are resolved at compile time, providing performance and type safety.
- Generic classes —
class Stack<T> - Generic methods —
T Max<T>(T a, T b) where T : IComparable - Constraints —
where T : class, new() - Multiple type parameters —
class Pair<K, V>
// Generics in C#
using System;
// Generic class
class Stack<T> {
private T[] data = new T[100];
private int top = -1;
public void Push(T val) {
data[++top] = val;
}
public T Pop() {
return data[top--];
}
public T Peek() {
return data[top];
}
public bool IsEmpty() {
return top == -1;
}
}
// Generic method
class Program {
static T Max<T>(T a, T b) where T : IComparable<T> {
return a.CompareTo(b) > 0 ? a : b;
}
static void Swap<T>(ref T a, ref T b) {
T temp = a;
a = b;
b = temp;
}
// Multiple type parameters
class Pair<K, V> {
public K Key { get; set; }
public V Value { get; set; }
public Pair(K key, V value) {
Key = key;
Value = value;
}
public void Print() {
Console.WriteLine($"{Key} -> {Value}");
}
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max(3.5, 2.1));
Console.WriteLine(Max("B", "A"));
Stack<int> si = new Stack<int>();
si.Push(1); si.Push(2); si.Push(3);
Console.WriteLine($"{si.Pop()} {si.Pop()}");
Pair<string, int> p = new Pair<string, int>("age", 25);
p.Print();
}
}A List<T> is a dynamic array from the System.Collections.Generic namespace. It provides O(1) random access and integrates with LINQ for powerful data queries.
Add()— add to endInsert()— add at position O(n)Remove()— remove elementSort()— sort in place- LINQ:
Where(),Select(),OrderBy()
// Collections - Lists
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
List<int> list = new List<int> { 5, 2, 8, 1, 9, 3 };
// Add elements
list.Add(7);
list.Insert(0, 0);
// Count and capacity
Console.WriteLine($"Count: {list.Count}");
Console.WriteLine($"Capacity: {list.Capacity}");
// Iterate
foreach (int x in list) Console.Write($"{x} ");
Console.WriteLine();
// Sort
list.Sort();
foreach (int x in list) Console.Write($"{x} ");
Console.WriteLine();
// Find and remove
list.Remove(8);
// 2D List
List<List<int>> mat = new List<List<int>>();
for (int i = 0; i < 3; i++) {
mat.Add(new List<int>(new int[3]));
}
mat[1][1] = 5;
Console.WriteLine($"mat[1][1] = {mat[1][1]}");
// LINQ
var evens = list.Where(x => x % 2 == 0).ToList();
Console.WriteLine("Evens: " + string.Join(", ", evens));
}
}Dictionary<TKey, TValue> is a hash-based key-value store with O(1) average operations. HashSet<T> stores unique elements with O(1) lookups.
Dictionary— key-value pairs, O(1) averageHashSet— unique elements, O(1) averageSortedDictionary— ordered by keyTryGetValue()— safe lookup without exception
// Dictionary and HashSet
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
// Dictionary (HashMap)
Dictionary<string, int> scores = new Dictionary<string, int>();
scores["Alice"] = 95;
scores["Bob"] = 87;
scores["Carol"] = 92;
foreach (var kvp in scores) {
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
Console.WriteLine($"Alice: {scores["Alice"]}");
Console.WriteLine($"Contains Bob: {scores.ContainsKey("Bob")}");
// HashSet
HashSet<int> set = new HashSet<int> { 5, 2, 8, 2, 1, 9, 5 };
foreach (int x in set) Console.Write($"{x} ");
Console.WriteLine();
set.Add(6);
set.Remove(2);
Console.WriteLine($"Contains 5: {set.Contains(5)}");
// SortedDictionary
SortedDictionary<string, int> sorted = new SortedDictionary<string, int>();
sorted["banana"] = 3;
sorted["apple"] = 5;
sorted["cherry"] = 2;
foreach (var kvp in sorted) {
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
}
}C# provides Stack (LIFO), Queue (FIFO), and PriorityQueue (ordered by priority) as part of System.Collections.Generic.
Stack:Push(),Pop(),Peek()Queue:Enqueue(),Dequeue(),Peek()PriorityQueue:Enqueue(item, priority),Dequeue()LinkedList— doubly-linked list
// Stack, Queue, and PriorityQueue
using System;
using System.Collections.Generic;
class Program {
static void Main() {
// Stack (LIFO)
Stack<int> stack = new Stack<int>();
stack.Push(10);
stack.Push(20);
stack.Push(30);
Console.WriteLine($"Stack top: {stack.Peek()}");
while (stack.Count > 0) {
Console.Write($"{stack.Pop()} ");
}
Console.WriteLine();
// Queue (FIFO)
Queue<int> queue = new Queue<int>();
queue.Enqueue(10);
queue.Enqueue(20);
queue.Enqueue(30);
Console.WriteLine($"Queue front: {queue.Peek()}");
while (queue.Count > 0) {
Console.Write($"{queue.Dequeue()} ");
}
Console.WriteLine();
// PriorityQueue (.NET 6+)
PriorityQueue<string, int> pq = new PriorityQueue<string, int>();
pq.Enqueue("Low", 3);
pq.Enqueue("High", 1);
pq.Enqueue("Medium", 2);
while (pq.Count > 0) {
var item = pq.Dequeue();
Console.Write($"{item} ");
}
Console.WriteLine();
// LinkedList (Doubly-linked)
LinkedList<int> ll = new LinkedList<int>();
ll.AddLast(10);
ll.AddLast(20);
ll.AddFirst(5);
foreach (int x in ll) Console.Write($"{x} ");
Console.WriteLine();
}
}Exception handling uses try, catch, finally, and throw keywords. Custom exceptions inherit from Exception or ApplicationException.
throw— raises an exceptioncatch— handles specific exception typesfinally— always executes (cleanup)- Custom exceptions — inherit from
Exception
// Exception Handling
using System;
// Custom exception
class ValidationError : Exception {
public int Code { get; }
public ValidationError(string message, int code) : base(message) {
Code = code;
}
}
class Program {
static double Divide(double a, double b) {
if (b == 0) throw new ArgumentException("Division by zero!");
return a / b;
}
static int GetAge(int age) {
if (age < 0 || age > 150)
throw new ValidationError($"Invalid age: {age}", 400);
return age;
}
static void Main() {
// Basic try-catch
try {
Console.WriteLine(Divide(10, 2));
Console.WriteLine(Divide(10, 0)); // throws
} catch (ArgumentException e) {
Console.WriteLine($"Error: {e.Message}");
}
// Custom exception
try {
GetAge(200);
} catch (ValidationError e) {
Console.WriteLine($"Validation [{e.Code}]: {e.Message}");
} catch (Exception e) {
Console.WriteLine($"General: {e.Message}");
}
// Finally block
try {
Console.WriteLine("Processing...");
} finally {
Console.WriteLine("Cleanup always runs");
}
}
}IDisposable provides deterministic resource cleanup. The using statement ensures Dispose() is called even when exceptions occur, following the RAII pattern.
IDisposable— implement for resource cleanupusingstatement — automatic disposalusingdeclaration (C# 8+) — scoped disposal- Used for file handles, database connections, mutexes
// IDisposable and Using Statement
using System;
class Resource : IDisposable {
private string name;
public Resource(string name) {
this.name = name;
Console.WriteLine($"Resource acquired: {name}");
}
public void Use() {
Console.WriteLine($"Using: {name}");
}
public void Dispose() {
Console.WriteLine($"Resource released: {name}");
}
}
class Program {
static void Main() {
// Using statement (auto-dispose)
using (Resource r1 = new Resource("FileResource")) {
r1.Use();
} // Auto-disposed here
// Using declaration (C# 8+)
using Resource r2 = new Resource("DatabaseResource");
r2.Use();
// Auto-disposed at end of scope
// Try-finally equivalent
Resource r3 = null;
try {
r3 = new Resource("NetworkResource");
r3.Use();
} finally {
r3?.Dispose();
}
}
}Lambda expressions (() => {}) are anonymous functions used extensively with LINQ for data querying. Func and Action delegates handle lambda storage.
Func<T, R>— delegate with return valueAction<T>— delegate without return value- LINQ:
Where(),Select(),OrderBy(),Aggregate() - Closures capture variables from enclosing scope
// Lambda Expressions and LINQ
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
// Basic lambda
Action<string> greet = (name) => {
Console.WriteLine($"Hello, {name}!");
};
greet("Alice");
// Func delegate
Func<int, int, int> add = (x, y) => x + y;
Console.WriteLine($"Add: {add(10, 20)}");
// Lambda with closures
int x = 10;
Func<int> addX = () => x + 5;
Console.WriteLine($"addX: {addX()}");
// LINQ with lambda
List<int> nums = new List<int> { 5, 1, 8, 3, 9, 2, 7 };
// Sort
nums.Sort((a, b) => a.CompareTo(b));
Console.WriteLine(string.Join(" ", nums));
// Filter
var evens = nums.Where(n => n % 2 == 0).ToList();
Console.WriteLine($"Evens: {string.Join(" ", evens)}");
// Transform
var squares = nums.Select(n => n * n).ToList();
Console.WriteLine($"Squares: {string.Join(" ", squares)}");
// Aggregate
int sum = nums.Aggregate(0, (acc, n) => acc + n);
Console.WriteLine($"Sum: {sum}");
// Action and Func delegates
Action<int> print = n => Console.Write($"{n} ");
Func<int, int> doubleIt = n => n * 2;
}
}Async/Await (C# 5) enables non-blocking asynchronous programming using Task and Task<T>. The compiler transforms async methods into state machines.
async— marks a method as asynchronousawait— suspends execution until task completesTask.WhenAll()— wait for multiple tasksTask.Run()— run CPU-bound work on thread pool
// Async/Await and Tasks
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
class Program {
static async Task<string> FetchDataAsync(string url, int delay) {
await Task.Delay(delay);
return $"Data from {url}";
}
static async Task<int> ComputeAsync(int a, int b) {
await Task.Delay(100);
return a + b;
}
static async Task Main() {
// Basic async
string result = await FetchDataAsync("api.example.com", 500);
Console.WriteLine(result);
// Parallel async tasks
var tasks = new List<Task<string>> {
FetchDataAsync("source1", 300),
FetchDataAsync("source2", 200),
FetchDataAsync("source3", 400)
};
string[] results = await Task.WhenAll(tasks);
Console.WriteLine("All results: " + string.Join(", ", results));
// Task with exception handling
try {
await Task.Run(() => {
throw new InvalidOperationException("Task failed");
});
} catch (Exception e) {
Console.WriteLine($"Caught: {e.Message}");
}
// Parallel processing
var numbers = Enumerable.Range(1, 10).ToList();
var parallelResults = await Task.WhenAll(
numbers.Select(async n => {
await Task.Delay(50);
return n * n;
})
);
Console.WriteLine($"Squares: {string.Join(", ", parallelResults)}");
}
}C# provides File and FileInfo for static operations, and StreamReader/StreamWriter for streaming. The using statement ensures proper resource disposal.
File.ReadAllLines()— read all linesFile.WriteAllLines()— write all linesStreamReader/StreamWriter— streaming I/ODirectory— directory operations
// File I/O in C#
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
class Program {
static void Main() {
// Write to file
string path = "students.txt";
string[] lines = {
"Alice 20 3.85",
"Bob 22 3.62",
"Carol 21 3.91"
};
File.WriteAllLines(path, lines);
// Read from file
string[] content = File.ReadAllLines(path);
foreach (string line in content) {
Console.WriteLine(line);
}
// Read/Write with StreamReader/StreamWriter
using (StreamWriter sw = new StreamWriter("output.txt")) {
sw.WriteLine("Hello, World!");
sw.WriteLine("Line 2");
}
using (StreamReader sr = new StreamReader("output.txt")) {
string text = sr.ReadToEnd();
Console.WriteLine(text);
}
// File operations
if (File.Exists("temp.txt")) {
File.Delete("temp.txt");
}
// Directory operations
Directory.CreateDirectory("testdir");
Directory.Delete("testdir");
// FileInfo and DirectoryInfo
FileInfo fi = new FileInfo("students.txt");
Console.WriteLine($"File size: {fi.Length} bytes");
Console.WriteLine($"Created: {fi.CreationTime}");
// CSV parsing with StringReader
string csv = "Alice,Bob,Carol,Dave";
using (StringReader sr = new StringReader(csv)) {
string line;
while ((line = sr.ReadLine()) != null) {
foreach (string token in line.Split(',')) {
Console.Write($"{token} ");
}
Console.WriteLine();
}
}
}
}LINQ (Language Integrated Query) provides a SQL-like syntax for querying data. Advanced features include GroupJoin, SelectMany, ToLookup, and Aggregate.
GroupJoin— left outer joinSelectMany— flatten sequencesToLookup— key-based groupingAggregate— custom reduction/fold
// LINQ and Functional Programming
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
List<int> nums = new List<int> { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3 };
// LINQ queries
var unique = nums.Distinct().OrderBy(x => x).ToList();
Console.WriteLine($"Unique: {string.Join(" ", unique)}");
// Aggregation
Console.WriteLine($"Sum: {nums.Sum()}");
Console.WriteLine($"Min: {nums.Min()}");
Console.WriteLine($"Max: {nums.Max()}");
Console.WriteLine($"Avg: {nums.Average():F2}");
// Conditional counts
int evens = nums.Count(x => x % 2 == 0);
Console.WriteLine($"Evens: {evens}");
var firstGreater = nums.FirstOrDefault(x => x > 4);
Console.WriteLine($"First > 4: {firstGreater}");
// Transform
var doubled = nums.Select(x => x * 2).ToList();
Console.WriteLine($"Doubled: {string.Join(" ", doubled)}");
// Group by
var grouped = nums.GroupBy(x => x % 2 == 0 ? "Even" : "Odd");
foreach (var group in grouped) {
Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
}
// Zip
List<int> a = new List<int> { 1, 2, 3 };
List<int> b = new List<int> { 4, 5, 6 };
var zipped = a.Zip(b, (x, y) => x + y).ToList();
Console.WriteLine($"Zipped sum: {string.Join(", ", zipped)}");
// Query syntax
var query = from x in nums
where x > 3
orderby x descending
select x * 2;
Console.WriteLine($"Query: {string.Join(", ", query)}");
}
}A generic LinkedList in C# uses generics for type safety. The class implements PushFront and PushBack methods for O(1) insertions at both ends.
- Generic node with
DataandNext PushFront— O(1) insert at headPushBack— O(n) insert at tail- Built-in
LinkedList<T>inSystem.Collections.Generic
// LinkedList Implementation
using System;
using System.Collections.Generic;
class Node<T> {
public T Data { get; set; }
public Node<T> Next { get; set; }
public Node(T data) {
Data = data;
Next = null;
}
}
class LinkedList<T> {
private Node<T> head;
public void PushFront(T val) {
Node<T> node = new Node<T>(val);
node.Next = head;
head = node;
}
public void PushBack(T val) {
Node<T> node = new Node<T>(val);
if (head == null) {
head = node;
return;
}
Node<T> curr = head;
while (curr.Next != null) curr = curr.Next;
curr.Next = node;
}
public void Display() {
Node<T> curr = head;
while (curr != null) {
Console.Write($"{curr.Data} -> ");
curr = curr.Next;
}
Console.WriteLine("null");
}
}
class Program {
static void Main() {
LinkedList<int> list = new LinkedList<int>();
list.PushBack(10);
list.PushBack(20);
list.PushBack(30);
list.PushFront(5);
list.Display();
}
}C# provides built-in BinarySearch() and Sort() methods on List<T> and Array. Find and FindAll offer predicate-based searching.
List.BinarySearch()— O(log n) searchList.Sort()— O(n log n) in-place sortArray.Sort()— sort arraysFind()/FindAll()— predicate search
// Binary Search and Sorting
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
// Manual binary search
static int BinarySearch(List<int> arr, int target) {
int left = 0, right = arr.Count - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
static void Main() {
List<int> arr = new List<int> { 2, 5, 8, 12, 16, 23, 38, 56, 72, 91 };
// Manual
Console.WriteLine($"Index of 23: {BinarySearch(arr, 23)}");
// BinarySearch method
Console.WriteLine($"Index of 56: {arr.BinarySearch(56)}");
// Contains
Console.WriteLine($"Contains 56: {arr.Contains(56)}");
// Find methods
int found = arr.Find(x => x > 20);
Console.WriteLine($"First > 20: {found}");
// Find all
var greater = arr.FindAll(x => x > 30);
Console.WriteLine($" > 30: {string.Join(", ", greater)}");
// BinarySearch on custom comparer
arr.Sort();
int index = arr.BinarySearch(23);
Console.WriteLine($"Sorted index: {index}");
}
}Recursion in C# works the same as other languages — a function calling itself. C# supports tail recursion optimization in some cases and provides stackalloc for stack-allocated arrays.
- Always define a base case
- Each call creates a new stack frame
- Recursive
asyncmethods are possible - Use
yield returnfor lazy recursion
// Recursion in C#
using System;
class Program {
static int Factorial(int n) {
if (n <= 1) return 1;
return n * Factorial(n - 1);
}
static int Fibonacci(int n) {
if (n <= 1) return n;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
static void Hanoi(int n, char from, char to, char aux) {
if (n == 1) {
Console.WriteLine($"Move disk 1: {from} -> {to}");
return;
}
Hanoi(n - 1, from, aux, to);
Console.WriteLine($"Move disk {n}: {from} -> {to}");
Hanoi(n - 1, aux, to, from);
}
static int Power(int baseNum, int exp) {
if (exp == 0) return 1;
if (exp % 2 == 0) {
int half = Power(baseNum, exp / 2);
return half * half;
}
return baseNum * Power(baseNum, exp - 1);
}
static void Main() {
Console.WriteLine($"5! = {Factorial(5)}");
Console.WriteLine($"fib(8) = {Fibonacci(8)}");
Console.WriteLine($"2^10 = {Power(2, 10)}");
Console.WriteLine("Tower of Hanoi (3 disks):");
Hanoi(3, 'A', 'C', 'B');
}
}C# provides built-in sorting via List.Sort() and Array.Sort(). Custom implementations of Bubble Sort, Merge Sort, and Quicksort are common for learning algorithms.
- Bubble Sort — O(n²), stable
- Merge Sort — O(n log n), stable, extra space
- Built-in
Sort()— O(n log n), uses introsort - Custom comparers for complex objects
// Sorting Algorithms in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void BubbleSort(List<int> arr) {
int n = arr.Count;
for (int i = 0; i < n - 1; i++) {
bool swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break;
}
}
static void MergeSort(List<int> arr, int l, int r) {
if (l >= r) return;
int m = l + (r - l) / 2;
MergeSort(arr, l, m);
MergeSort(arr, m + 1, r);
List<int> tmp = new List<int>();
int i = l, j = m + 1;
while (i <= m && j <= r)
tmp.Add(arr[i] <= arr[j] ? arr[i++] : arr[j++]);
while (i <= m) tmp.Add(arr[i++]);
while (j <= r) tmp.Add(arr[j++]);
for (int k = l; k <= r; k++) arr[k] = tmp[k - l];
}
static void Main() {
List<int> v1 = new List<int> { 64, 34, 25, 12, 22, 11, 90 };
BubbleSort(v1);
Console.WriteLine($"Bubble: {string.Join(" ", v1)}");
List<int> v2 = new List<int> { 38, 27, 43, 3, 9, 82, 10 };
MergeSort(v2, 0, v2.Count - 1);
Console.WriteLine($"Merge: {string.Join(" ", v2)}");
// Built-in sort
List<int> v3 = new List<int> { 5, 3, 1, 8, 2, 7 };
v3.Sort();
Console.WriteLine($"Built-in: {string.Join(" ", v3)}");
// Sort with comparison
v3.Sort((a, b) => b.CompareTo(a));
Console.WriteLine($"Descending: {string.Join(" ", v3)}");
}
}C# uses garbage collection for automatic memory management. The new keyword allocates on the heap, and the GC automatically reclaims memory when objects are no longer referenced.
new— allocate memory and call constructor- Garbage Collector — automatic memory reclamation
using— deterministic cleanup forIDisposableGC.Collect()— force collection (rarely needed)
// Dynamic Memory and Garbage Collection
using System;
class Matrix : IDisposable {
private int[,] data;
private int rows, cols;
public Matrix(int rows, int cols) {
this.rows = rows;
this.cols = cols;
data = new int[rows, cols];
}
public void Set(int r, int c, int val) {
data[r, c] = val;
}
public int Get(int r, int c) {
return data[r, c];
}
public void Print() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
Console.Write($"{data[i, j]} ");
}
Console.WriteLine();
}
}
public void Dispose() {
// Clean up unmanaged resources if any
}
}
class Program {
static void Main() {
// Arrays
int[] arr = new int[5] { 10, 20, 30, 40, 50 };
Console.WriteLine(string.Join(" ", arr));
// Multi-dimensional array
int[,] matrix = new int[3, 3];
matrix[0, 0] = 1;
matrix[1, 1] = 5;
matrix[2, 2] = 9;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
Console.Write($"{matrix[i, j]} ");
}
Console.WriteLine();
}
// Jagged array
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5 };
jagged[2] = new int[] { 6 };
// Garbage Collection
Console.WriteLine($"Total memory: {GC.GetTotalMemory(false)}");
GC.Collect();
Console.WriteLine($"After GC: {GC.GetTotalMemory(false)}");
}
}C# string is immutable, making operations like concatenation expensive. StringBuilder provides mutable string operations for performance-sensitive scenarios.
Substring(),IndexOf(),Contains()Split(),Join(),Replace()StringBuilder— mutable strings- String interpolation
$"{name}"
// String Operations in C#
using System;
using System.Text;
using System.Linq;
class Program {
static void Main() {
string s = "Hello, World!";
// Basic operations
Console.WriteLine($"Length: {s.Length}");
Console.WriteLine($"Substring: {s.Substring(7, 5)}");
Console.WriteLine($"Contains: {s.Contains("World")}");
Console.WriteLine($"Index of: {s.IndexOf("World")}");
// StringBuilder (mutable string)
StringBuilder sb = new StringBuilder("Hello");
sb.Append(", World!");
sb.Insert(6, " C#");
sb.Replace("World", "Universe");
Console.WriteLine(sb.ToString());
// Case conversion
string lower = s.ToLower();
string upper = s.ToUpper();
Console.WriteLine($"Lower: {lower}");
Console.WriteLine($"Upper: {upper}");
// Split and join
string csv = "Alice,Bob,Carol,Dave";
string[] tokens = csv.Split(',');
Console.WriteLine(string.Join(" ", tokens));
// String interpolation
string name = "Alice";
int age = 25;
Console.WriteLine($"{name} is {age} years old");
// Trim and padding
string padded = " Hello ";
Console.WriteLine($"Trimmed: '{padded.Trim()}'");
Console.WriteLine($"Padded: '{padded.PadLeft(10)}'");
// Reverse and palindrome
string pal = "racecar";
char[] arr = pal.ToCharArray();
Array.Reverse(arr);
string reversed = new string(arr);
Console.WriteLine($"{pal} is palindrome: {pal == reversed}");
}
}Interfaces define contracts without implementation. Abstract classes provide partial implementation and cannot be instantiated. C# supports multiple interface inheritance but single class inheritance.
- Interface — pure contract, multiple inheritance
- Abstract class — can have implementation, single inheritance
- Default interface methods (C# 8+)
- Used for polymorphism and dependency injection
// Interfaces and Abstract Classes
using System;
// Interface
interface IDrawable {
void Draw();
void Resize(double factor);
double Area { get; }
}
// Abstract class
abstract class Shape : IDrawable {
public abstract void Draw();
public abstract void Resize(double factor);
public abstract double Area { get; }
}
class Circle : Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public override void Draw() {
Console.WriteLine($"Drawing Circle r={radius}");
}
public override void Resize(double factor) {
radius *= factor;
}
public override double Area {
get { return Math.PI * radius * radius; }
}
}
class Square : Shape {
private double side;
public Square(double side) {
this.side = side;
}
public override void Draw() {
Console.WriteLine($"Drawing Square s={side}");
}
public override void Resize(double factor) {
side *= factor;
}
public override double Area {
get { return side * side; }
}
}
class Program {
static void Main() {
List<Shape> shapes = new List<Shape> {
new Circle(5.0),
new Square(4.0)
};
foreach (var s in shapes) {
s.Draw();
Console.WriteLine($"Area: {s.Area:F2}");
s.Resize(2.0);
s.Draw();
Console.WriteLine($"New Area: {s.Area:F2}");
}
}
}C# does not support multiple class inheritance but allows multiple interface inheritance. A class can implement multiple interfaces, combining capabilities from different sources.
- Implement multiple interfaces with
:syntax - Explicit interface implementation for conflicts
- Default interface methods (C# 8+)
- Used for mixins and capability composition
// Multiple Inheritance via Interfaces
using System;
interface IVehicle {
int Speed { get; set; }
void Move();
}
interface IElectric {
int Battery { get; set; }
void Charge();
}
// Multiple interface inheritance
class ElectricCar : IVehicle, IElectric {
private string model;
public int Speed { get; set; }
public int Battery { get; set; }
public ElectricCar(string model, int speed, int battery) {
this.model = model;
Speed = speed;
Battery = battery;
}
public void Display() {
Console.WriteLine($"Model: {model}");
Console.WriteLine($"Speed: {Speed} km/h");
Console.WriteLine($"Battery: {Battery}%");
}
public void Move() {
Console.WriteLine($"{model} glides silently at {Speed} km/h");
}
public void Charge() {
Console.WriteLine($"Charging battery: {Battery}%");
}
}
class Program {
static void Main() {
ElectricCar tesla = new ElectricCar("Tesla Model 3", 250, 85);
tesla.Display();
tesla.Move();
tesla.Charge();
}
}Extension methods allow adding methods to existing types without modifying them. They are defined as static methods in static classes and use the this keyword.
- Must be in static class
- First parameter uses
thiskeyword - Cannot access private members
- Used for LINQ, adding convenience methods
// Extension Methods and LINQ
using System;
using System.Collections.Generic;
using System.Linq;
public static class StringExtensions {
public static bool IsPalindrome(this string s) {
string cleaned = new string(s.Where(char.IsLetterOrDigit).ToArray()).ToLower();
return cleaned.SequenceEqual(cleaned.Reverse());
}
public static string ToTitleCase(this string s) {
return char.ToUpper(s[0]) + s.Substring(1).ToLower();
}
}
public static class ListExtensions {
public static T Second<T>(this List<T> list) {
if (list.Count < 2) throw new InvalidOperationException("List has less than 2 elements");
return list[1];
}
public static T LastOrDefault<T>(this List<T> list, T defaultValue) {
return list.Count > 0 ? list[list.Count - 1] : defaultValue;
}
}
class Program {
static void Main() {
// Extension methods on strings
string text = "racecar";
Console.WriteLine($"{text} is palindrome: {text.IsPalindrome()}");
string name = "alice";
Console.WriteLine($"Title case: {name.ToTitleCase()}");
// Extension methods on lists
List<int> numbers = new List<int> { 10, 20, 30, 40 };
Console.WriteLine($"Second: {numbers.Second()}");
Console.WriteLine($"LastOrDefault(100): {numbers.LastOrDefault(100)}");
List<int> empty = new List<int>();
Console.WriteLine($"Empty LastOrDefault: {empty.LastOrDefault(100)}");
// Chaining with LINQ
var result = numbers
.Where(x => x > 15)
.Select(x => x * 2)
.ToList();
Console.WriteLine($"Chained: {string.Join(", ", result)}");
}
}The Singleton pattern ensures only one instance exists. The Factory pattern creates objects without exposing creation logic. Both are widely used in C# applications.
- Singleton: private constructor + static instance
- Thread-safe singleton with
Lazy<T> - Factory: returns interface/abstract class
- Used for dependency injection and object creation
// Design Patterns - Singleton and Factory
using System;
// Singleton
class Config {
private static Config instance;
private string dbUrl = "localhost:5432";
private Config() { }
public static Config GetInstance() {
if (instance == null) instance = new Config();
return instance;
}
public string DbUrl { get { return dbUrl; } set { dbUrl = value; } }
}
// Factory Pattern
interface ILogger {
void Log(string message);
}
class ConsoleLogger : ILogger {
public void Log(string message) {
Console.WriteLine($"[CONSOLE] {message}");
}
}
class FileLogger : ILogger {
public void Log(string message) {
Console.WriteLine($"[FILE] {message}");
}
}
class LoggerFactory {
public static ILogger CreateLogger(string type) {
return type.ToLower() switch {
"console" => new ConsoleLogger(),
"file" => new FileLogger(),
_ => null
};
}
}
class Program {
static void Main() {
Config cfg = Config.GetInstance();
Console.WriteLine(cfg.DbUrl);
ILogger logger = LoggerFactory.CreateLogger("console");
logger.Log("App started");
ILogger flog = LoggerFactory.CreateLogger("file");
flog.Log("Error occurred");
}
}Namespaces organize code and prevent naming conflicts. The using directive imports namespaces, and using alias creates shorter references.
namespace Name— define namespaceusing— import namespaceusing Name = Full.Name— alias- Nested namespaces with
.syntax
// Namespaces and Using
using System;
using System.Collections.Generic;
namespace MathUtils {
public const double PI = 3.14159265358979;
public static class Geometry {
public static double CircleArea(double r) {
return PI * r * r;
}
public static double RectArea(double w, double h) {
return w * h;
}
}
namespace Advanced {
public static class Algebra {
public static double Power(double baseNum, int exp) {
double result = 1;
for (int i = 0; i < exp; i++) result *= baseNum;
return result;
}
}
}
}
namespace Physics {
public const double G = 9.81;
public static class Mechanics {
public static double KineticEnergy(double m, double v) {
return 0.5 * m * v * v;
}
public static double Weight(double mass) {
return mass * G;
}
}
}
class Program {
static void Main() {
using MathUtils.Geometry;
using MathUtils.Advanced;
Console.WriteLine($"PI = {MathUtils.PI}");
Console.WriteLine($"Circle area = {Geometry.CircleArea(5.0)}");
Console.WriteLine($"2^8 = {Algebra.Power(2, 8)}");
Console.WriteLine($"Weight(70kg) = {Physics.Mechanics.Weight(70)} N");
// Using alias
using Math = MathUtils.Geometry;
Console.WriteLine($"Rect area = {Math.RectArea(4, 5)}");
}
}The Two Sum problem uses a Dictionary for O(n) lookup. For each element, check if the complement exists in the dictionary for optimal performance.
- Dictionary approach: O(n) time, O(n) space
- Two pointer (sorted): O(n log n) time, O(1) space
- Tuples for clean pair returns
- Common coding interview question
// Two Sum Problem in C#
using System;
using System.Collections.Generic;
class Program {
// Hash map approach O(n)
static int[] TwoSum(int[] nums, int target) {
Dictionary<int, int> map = new Dictionary<int, int>();
for (int i = 0; i < nums.Length; i++) {
int complement = target - nums[i];
if (map.ContainsKey(complement))
return new int[] { map[complement], i };
map[nums[i]] = i;
}
return new int[] { };
}
// Two pointer (sorted input)
static List<(int, int)> TwoSumPairs(int[] arr, int target) {
List<(int, int)> result = new List<(int, int)>();
int l = 0, r = arr.Length - 1;
while (l < r) {
int sum = arr[l] + arr[r];
if (sum == target) {
result.Add((arr[l], arr[r]));
l++; r--;
} else if (sum < target) l++;
else r--;
}
return result;
}
static void Main() {
int[] nums = { 2, 7, 11, 15 };
int[] res = TwoSum(nums, 9);
Console.WriteLine($"Indices: [{res[0]}, {res[1]}]");
int[] sorted = { 1, 2, 3, 4, 6 };
foreach (var (a, b) in TwoSumPairs(sorted, 6))
Console.WriteLine($"Pair: {a} + {b}");
}
}Kadane's Algorithm finds the maximum sum contiguous subarray in O(n) time using tuple returns for the sum and indices. Uses int.MinValue for initialization.
- Track current sum and maximum sum
- Reset current sum when negative
- Return tuple with sum, start, end
- Time O(n), Space O(1)
// Kadane's Algorithm in C#
using System;
using System.Collections.Generic;
class Program {
static (int sum, int start, int end) MaxSubarray(int[] arr) {
int maxSum = int.MinValue, currSum = 0;
int start = 0, end = 0, tempStart = 0;
for (int i = 0; i < arr.Length; i++) {
currSum += arr[i];
if (currSum > maxSum) {
maxSum = currSum;
start = tempStart;
end = i;
}
if (currSum < 0) {
currSum = 0;
tempStart = i + 1;
}
}
return (maxSum, start, end);
}
static void Main() {
int[] arr = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };
var (sum, s, e) = MaxSubarray(arr);
Console.WriteLine($"Max Sum: {sum}");
Console.Write("Subarray: ");
for (int i = s; i <= e; i++) Console.Write($"{arr[i]} ");
Console.WriteLine();
}
}A Binary Tree uses a TreeNode class with left and right references. Level-order insertion uses a Queue to fill the tree level by level.
TreeNodewithLeftandRight- Level-order insertion with
Queue - Inorder traversal: left, root, right
- Height: 1 + max(left, right)
// Binary Tree in C#
using System;
using System.Collections.Generic;
class TreeNode {
public int Val { get; set; }
public TreeNode Left { get; set; }
public TreeNode Right { get; set; }
public TreeNode(int val) {
Val = val;
Left = null;
Right = null;
}
}
class BinaryTree {
private TreeNode root;
public BinaryTree() {
root = null;
}
public void Insert(int val) {
TreeNode node = new TreeNode(val);
if (root == null) {
root = node;
return;
}
Queue<TreeNode> q = new Queue<TreeNode>();
q.Enqueue(root);
while (q.Count > 0) {
TreeNode curr = q.Dequeue();
if (curr.Left == null) {
curr.Left = node;
return;
} else q.Enqueue(curr.Left);
if (curr.Right == null) {
curr.Right = node;
return;
} else q.Enqueue(curr.Right);
}
}
public void Inorder() {
Inorder(root);
Console.WriteLine();
}
private void Inorder(TreeNode node) {
if (node == null) return;
Inorder(node.Left);
Console.Write($"{node.Val} ");
Inorder(node.Right);
}
public int Height() {
return Height(root);
}
private int Height(TreeNode node) {
if (node == null) return 0;
return 1 + Math.Max(Height(node.Left), Height(node.Right));
}
}
class Program {
static void Main() {
BinaryTree bt = new BinaryTree();
foreach (int v in new int[] { 1, 2, 3, 4, 5, 6, 7 }) bt.Insert(v);
bt.Inorder();
Console.WriteLine($"Height: {bt.Height()}");
}
}A BST in C# uses recursive Insert and Search methods. Inorder traversal produces sorted output, confirming the tree structure.
- Insert: recursively go left/right based on value
- Search: O(log n) average, O(n) worst
- Inorder: left, root, right
- Static methods for clean API
// Binary Search Tree in C#
using System;
class BST {
public int Val { get; set; }
public BST Left { get; set; }
public BST Right { get; set; }
public BST(int val) {
Val = val;
Left = null;
Right = null;
}
}
class Program {
static BST Insert(BST root, int val) {
if (root == null) return new BST(val);
if (val < root.Val) root.Left = Insert(root.Left, val);
else if (val > root.Val) root.Right = Insert(root.Right, val);
return root;
}
static bool Search(BST root, int val) {
if (root == null) return false;
if (root.Val == val) return true;
return val < root.Val ? Search(root.Left, val)
: Search(root.Right, val);
}
static void Inorder(BST root) {
if (root == null) return;
Inorder(root.Left);
Console.Write($"{root.Val} ");
Inorder(root.Right);
}
static void Main() {
BST root = null;
foreach (int v in new int[] { 50, 30, 70, 20, 40, 60, 80 })
root = Insert(root, v);
Inorder(root);
Console.WriteLine();
Console.WriteLine($"Search 40: {(Search(root, 40) ? "Found" : "Not found")}");
Console.WriteLine($"Search 99: {(Search(root, 99) ? "Found" : "Not found")}");
}
}A Graph class uses List<int>[] for adjacency lists. BFS uses a Queue, DFS uses recursion with a helper method.
- Adjacency list:
List<int>[] - BFS: queue + visited array
- DFS: recursion + visited array
- Time Complexity O(V+E)
// Graph BFS and DFS in C#
using System;
using System.Collections.Generic;
class Graph {
private int V;
private List<int>[] adj;
public Graph(int v) {
V = v;
adj = new List<int>[v];
for (int i = 0; i < v; i++) adj[i] = new List<int>();
}
public void AddEdge(int u, int v) {
adj[u].Add(v);
adj[v].Add(u);
}
public void BFS(int start) {
bool[] visited = new bool[V];
Queue<int> q = new Queue<int>();
visited[start] = true;
q.Enqueue(start);
Console.Write("BFS: ");
while (q.Count > 0) {
int v = q.Dequeue();
Console.Write($"{v} ");
foreach (int u in adj[v])
if (!visited[u]) {
visited[u] = true;
q.Enqueue(u);
}
}
Console.WriteLine();
}
private void DFSHelper(int v, bool[] visited) {
visited[v] = true;
Console.Write($"{v} ");
foreach (int u in adj[v])
if (!visited[u]) DFSHelper(u, visited);
}
public void DFS(int start) {
bool[] visited = new bool[V];
Console.Write("DFS: ");
DFSHelper(start, visited);
Console.WriteLine();
}
}
class Program {
static void Main() {
Graph g = new Graph(6);
g.AddEdge(0, 1); g.AddEdge(0, 2);
g.AddEdge(1, 3); g.AddEdge(2, 4); g.AddEdge(3, 5);
g.BFS(0);
g.DFS(0);
}
}Dijkstra's Algorithm uses a SortedSet as a min-heap for O((V+E) log V) performance. Tuples with priority values enable clean heap operations.
SortedSet<(int dist, int node)>as min-heap- Remove old entries before adding updated distances
- Time Complexity O((V+E) log V)
- Only works with non-negative edge weights
// Dijkstra's Algorithm in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Dijkstra(List<(int, int)>[] graph, int src) {
int V = graph.Length;
int[] dist = Enumerable.Repeat(int.MaxValue, V).ToArray();
var pq = new SortedSet<(int dist, int node)>();
dist[src] = 0;
pq.Add((0, src));
while (pq.Count > 0) {
var (d, u) = pq.Min;
pq.Remove(pq.Min);
if (d > dist[u]) continue;
foreach (var (w, v) in graph[u]) {
if (dist[u] + w < dist[v]) {
pq.Remove((dist[v], v));
dist[v] = dist[u] + w;
pq.Add((dist[v], v));
}
}
}
Console.WriteLine($"Shortest distances from {src}:");
for (int i = 0; i < V; i++)
Console.WriteLine($" To {i}: {(dist[i] == int.MaxValue ? -1 : dist[i])}");
}
static void Main() {
int V = 5;
var graph = new List<(int, int)>[V];
for (int i = 0; i < V; i++) graph[i] = new List<(int, int)>();
void AddEdge(int u, int v, int w) {
graph[u].Add((w, v));
graph[v].Add((w, u));
}
AddEdge(0, 1, 10); AddEdge(0, 3, 5);
AddEdge(1, 2, 1); AddEdge(1, 3, 2);
AddEdge(2, 4, 4); AddEdge(3, 4, 9);
Dijkstra(graph, 0);
}
}C# 2D arrays (int[,]) are clean for DP tables. 0/1 Knapsack and LCS are foundational DP problems solved with bottom-up tabulation.
- Knapsack: maximize value within weight capacity
- LCS: longest common subsequence
- Time Complexity O(n*W) knapsack, O(m*n) LCS
Math.Max()for clean comparisons
// Dynamic Programming - Classic Problems
using System;
using System.Collections.Generic;
class Program {
// 0/1 Knapsack
static int Knapsack(int[] weights, int[] values, int W) {
int n = weights.Length;
int[,] dp = new int[n + 1, W + 1];
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= W; j++) {
dp[i, j] = dp[i - 1, j];
if (weights[i - 1] <= j)
dp[i, j] = Math.Max(dp[i, j],
dp[i - 1, j - weights[i - 1]] + values[i - 1]);
}
}
return dp[n, W];
}
// Longest Common Subsequence
static int LCS(string s1, string s2) {
int m = s1.Length, n = s2.Length;
int[,] dp = new int[m + 1, n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i, j] = s1[i - 1] == s2[j - 1]
? dp[i - 1, j - 1] + 1
: Math.Max(dp[i - 1, j], dp[i, j - 1]);
}
}
return dp[m, n];
}
static void Main() {
int[] weights = { 1, 3, 4, 5 };
int[] values = { 1, 4, 5, 7 };
Console.WriteLine($"Knapsack(W=7): {Knapsack(weights, values, 7)}");
string s1 = "ABCBDAB", s2 = "BDCAB";
Console.WriteLine($"LCS: {LCS(s1, s2)}");
}
}A custom HashMap uses List<KeyValuePair<K, V>>[] for chaining. The hash function uses GetHashCode() with modulo for bucket indexing.
GetHashCode()for key hashing- Chaining with
Listper bucket RemoveAll()for clean deletion- Average O(1) operations
// Hash Map - Custom Implementation
using System;
using System.Collections.Generic;
class HashMap<K, V> {
private List<KeyValuePair<K, V>>[] table;
private int capacity;
public HashMap(int cap = 16) {
capacity = cap;
table = new List<KeyValuePair<K, V>>[cap];
for (int i = 0; i < cap; i++) table[i] = new List<KeyValuePair<K, V>>();
}
private int Hash(K key) {
return Math.Abs(key.GetHashCode()) % capacity;
}
public void Put(K key, V value) {
int idx = Hash(key);
foreach (var kvp in table[idx]) {
if (kvp.Key.Equals(key)) {
table[idx].Remove(kvp);
break;
}
}
table[idx].Add(new KeyValuePair<K, V>(key, value));
}
public V Get(K key) {
int idx = Hash(key);
foreach (var kvp in table[idx]) {
if (kvp.Key.Equals(key)) return kvp.Value;
}
throw new KeyNotFoundException($"Key '{key}' not found");
}
public bool Contains(K key) {
int idx = Hash(key);
foreach (var kvp in table[idx]) {
if (kvp.Key.Equals(key)) return true;
}
return false;
}
public void Remove(K key) {
int idx = Hash(key);
table[idx].RemoveAll(kvp => kvp.Key.Equals(key));
}
}
class Program {
static void Main() {
HashMap<string, int> map = new HashMap<string, int>();
map.Put("alice", 90);
map.Put("bob", 85);
map.Put("carol", 92);
Console.WriteLine($"alice: {map.Get("alice")}");
Console.WriteLine($"Contains bob: {map.Contains("bob")}");
map.Remove("bob");
Console.WriteLine($"Contains bob after remove: {map.Contains("bob")}");
}
}C# provides PriorityQueue in .NET 6+. For K Largest Elements, use a min-heap (SortedSet) of size k. Merge K Sorted Arrays uses a custom priority queue.
PriorityQueue(.NET 6+) — O(log n) operationsSortedSet— min-heap with custom comparer- K Largest: min-heap of size k
- Merge K sorted: tuple with value, array index, element index
// Heap and Priority Queue in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
// K largest elements
static List<int> KLargest(List<int> arr, int k) {
var minHeap = new SortedSet<(int val, int idx)>();
for (int i = 0; i < arr.Count; i++) {
minHeap.Add((arr[i], i));
if (minHeap.Count > k) minHeap.Remove(minHeap.Min);
}
return minHeap.Select(x => x.val).ToList();
}
// Merge K sorted arrays
static List<int> MergeKSorted(List<int>[] arrays) {
var pq = new SortedSet<(int val, int arrIdx, int elemIdx)>();
for (int i = 0; i < arrays.Length; i++) {
if (arrays[i].Count > 0)
pq.Add((arrays[i][0], i, 0));
}
List<int> result = new List<int>();
while (pq.Count > 0) {
var (val, i, j) = pq.Min;
pq.Remove(pq.Min);
result.Add(val);
if (j + 1 < arrays[i].Count)
pq.Add((arrays[i][j + 1], i, j + 1));
}
return result;
}
static void Main() {
List<int> arr = new List<int> { 3, 1, 5, 12, 2, 11, 9 };
var top3 = KLargest(arr, 3);
Console.WriteLine($"Top 3: {string.Join(" ", top3)}");
List<int>[] kArr = {
new List<int> { 1, 4, 7 },
new List<int> { 2, 5, 8 },
new List<int> { 3, 6, 9 }
};
var merged = MergeKSorted(kArr);
Console.WriteLine($"Merged: {string.Join(" ", merged)}");
}
}A Trie in C# uses Dictionary<char, TrieNode> for children. This is more flexible than a fixed 26-element array and handles any character set.
Dictionary<char, TrieNode>for childrenbool IsEndmarks end of wordInsert()andSearch()— O(L) per operation- Used in autocomplete and spell check
// Trie Data Structure in C#
using System;
using System.Collections.Generic;
class TrieNode {
public Dictionary<char, TrieNode> Children { get; set; }
public bool IsEnd { get; set; }
public TrieNode() {
Children = new Dictionary<char, TrieNode>();
IsEnd = false;
}
}
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void Insert(string word) {
TrieNode curr = root;
foreach (char c in word) {
if (!curr.Children.ContainsKey(c))
curr.Children[c] = new TrieNode();
curr = curr.Children[c];
}
curr.IsEnd = true;
}
public bool Search(string word) {
TrieNode curr = root;
foreach (char c in word) {
if (!curr.Children.ContainsKey(c)) return false;
curr = curr.Children[c];
}
return curr.IsEnd;
}
public bool StartsWith(string prefix) {
TrieNode curr = root;
foreach (char c in prefix) {
if (!curr.Children.ContainsKey(c)) return false;
curr = curr.Children[c];
}
return true;
}
}
class Program {
static void Main() {
Trie t = new Trie();
t.Insert("apple");
t.Insert("app");
t.Insert("apply");
Console.WriteLine($"Search apple: {t.Search("apple")}");
Console.WriteLine($"Search app: {t.Search("app")}");
Console.WriteLine($"Search ap: {t.Search("ap")}");
Console.WriteLine($"StartsWith appl: {t.StartsWith("appl")}");
Console.WriteLine($"StartsWith xyz: {t.StartsWith("xyz")}");
}
}A Segment Tree uses an array-based representation. Build, update, and query operations are implemented recursively for range sum queries.
- Build: O(n) time
- Query and Update: O(log n) time
- Tree stored in
int[]of size 4*n - Supports range sum, min, max queries
// Segment Tree in C#
using System;
using System.Collections.Generic;
class SegmentTree {
private int[] tree;
private int n;
public SegmentTree(int[] arr) {
n = arr.Length;
tree = new int[4 * n];
Build(arr, 1, 0, n - 1);
}
private void Build(int[] arr, int node, int l, int r) {
if (l == r) {
tree[node] = arr[l];
return;
}
int mid = (l + r) / 2;
Build(arr, node * 2, l, mid);
Build(arr, node * 2 + 1, mid + 1, r);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
public void Update(int idx, int val) {
Update(1, 0, n - 1, idx, val);
}
private void Update(int node, int l, int r, int idx, int val) {
if (l == r) {
tree[node] = val;
return;
}
int mid = (l + r) / 2;
if (idx <= mid) Update(node * 2, l, mid, idx, val);
else Update(node * 2 + 1, mid + 1, r, idx, val);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
public int Query(int ql, int qr) {
return Query(1, 0, n - 1, ql, qr);
}
private int Query(int node, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return 0;
if (ql <= l && r <= qr) return tree[node];
int mid = (l + r) / 2;
return Query(node * 2, l, mid, ql, qr) +
Query(node * 2 + 1, mid + 1, r, ql, qr);
}
}
class Program {
static void Main() {
int[] arr = { 1, 3, 5, 7, 9, 11 };
SegmentTree st = new SegmentTree(arr);
Console.WriteLine($"Sum [1,3]: {st.Query(1, 3)}");
st.Update(1, 10);
Console.WriteLine($"Sum [1,3] after update: {st.Query(1, 3)}");
}
}A Union-Find (Disjoint Set) uses path compression and union by rank. The Find() method recursively finds the root, and Unite() merges sets.
- Path compression:
parent[x] = Find(parent[x]) - Union by rank: attach smaller rank under larger
Connected()checks if two elements are in the same set- Used in Kruskal's MST and connectivity problems
// Union-Find in C#
using System;
class UnionFind {
private int[] parent;
private int[] rank;
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
}
public int Find(int x) {
if (parent[x] != x) parent[x] = Find(parent[x]);
return parent[x];
}
public bool Unite(int x, int y) {
int px = Find(x), py = Find(y);
if (px == py) return false;
if (rank[px] < rank[py]) (px, py) = (py, px);
parent[py] = px;
if (rank[px] == rank[py]) rank[px]++;
return true;
}
public bool Connected(int x, int y) {
return Find(x) == Find(y);
}
}
class Program {
static void Main() {
UnionFind uf = new UnionFind(6);
uf.Unite(0, 1); uf.Unite(1, 2); uf.Unite(3, 4);
Console.WriteLine($"0-2: {uf.Connected(0, 2)}");
Console.WriteLine($"0-3: {uf.Connected(0, 3)}");
uf.Unite(2, 3);
Console.WriteLine($"0-4 after merge: {uf.Connected(0, 4)}");
}
}The Sliding Window Maximum uses a LinkedList as a monotonic deque. It stores indices in decreasing order of value, achieving O(n) time.
LinkedList<int>as deque- Remove out-of-window indices from front
- Remove smaller elements from rear
- Front always has the current window maximum
// Sliding Window Maximum in C#
using System;
using System.Collections.Generic;
class Program {
static int[] MaxSlidingWindow(int[] nums, int k) {
LinkedList<int> dq = new LinkedList<int>(); // stores indices
List<int> result = new List<int>();
for (int i = 0; i < nums.Length; i++) {
// Remove out-of-window indices
while (dq.Count > 0 && dq.First.Value < i - k + 1)
dq.RemoveFirst();
// Remove smaller elements from rear
while (dq.Count > 0 && nums[dq.Last.Value] < nums[i])
dq.RemoveLast();
dq.AddLast(i);
if (i >= k - 1) result.Add(nums[dq.First.Value]);
}
return result.ToArray();
}
static void Main() {
int[] nums = { 1, 3, -1, -3, 5, 3, 6, 7 };
int k = 3;
int[] res = MaxSlidingWindow(nums, k);
Console.WriteLine($"Sliding window max: {string.Join(" ", res)}");
}
}The KMP Algorithm uses an LPS (Longest Proper Prefix which is Suffix) array to skip unnecessary comparisons, achieving O(n+m) time complexity.
- LPS array computed in O(m)
- Never moves backward in the text
- Returns all match positions
- Time O(n+m), Space O(m)
// KMP String Matching in C#
using System;
using System.Collections.Generic;
class Program {
static int[] BuildLPS(string pattern) {
int m = pattern.Length;
int[] lps = new int[m];
int len = 0, i = 1;
while (i < m) {
if (pattern[i] == pattern[len]) lps[i++] = ++len;
else if (len > 0) len = lps[len - 1];
else lps[i++] = 0;
}
return lps;
}
static List<int> KMPSearch(string text, string pattern) {
List<int> positions = new List<int>();
int[] lps = BuildLPS(pattern);
int n = text.Length, m = pattern.Length;
int i = 0, j = 0;
while (i < n) {
if (text[i] == pattern[j]) { i++; j++; }
if (j == m) {
positions.Add(i - j);
j = lps[j - 1];
} else if (i < n && text[i] != pattern[j]) {
if (j > 0) j = lps[j - 1];
else i++;
}
}
return positions;
}
static void Main() {
string text = "AABAACAADAABAABA";
string pat = "AABA";
var pos = KMPSearch(text, pat);
Console.WriteLine($"Pattern found at: {string.Join(" ", pos)}");
}
}The N-Queens problem uses backtracking with a 2D array board. The IsSafe() method checks row and diagonals before placing a queen.
- 2D array
int[,]for board - Check row and both diagonals
- Backtrack by resetting cell to 0
- 8-Queens has 92 solutions
// N-Queens in C#
using System;
class NQueens {
private int n;
private int[,] board;
private int solutions = 0;
public NQueens(int n) {
this.n = n;
board = new int[n, n];
}
private bool IsSafe(int row, int col) {
for (int j = 0; j < col; j++)
if (board[row, j] == 1) return false;
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--)
if (board[i, j] == 1) return false;
for (int i = row, j = col; i < n && j >= 0; i++, j--)
if (board[i, j] == 1) return false;
return true;
}
private void Solve(int col) {
if (col == n) {
solutions++;
if (solutions == 1) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)
Console.Write(board[i, j] == 1 ? "Q " : ". ");
Console.WriteLine();
}
}
return;
}
for (int row = 0; row < n; row++) {
if (IsSafe(row, col)) {
board[row, col] = 1;
Solve(col + 1);
board[row, col] = 0;
}
}
}
public void Run() {
Solve(0);
Console.WriteLine($"Total solutions: {solutions}");
}
}
class Program {
static void Main() {
NQueens q = new NQueens(8);
q.Run();
}
}An LRU Cache in C# uses a LinkedList for O(1) move-to-front and a Dictionary for O(1) key lookup. Combined they achieve O(1) get and put.
LinkedListfor cache orderDictionarymapping key to node- Evict least recently used when full
- Time O(1) for both operations
// LRU Cache in C#
using System;
using System.Collections.Generic;
class LRUCache<K, V> {
private int capacity;
private LinkedList<(K key, V value)> cache;
private Dictionary<K, LinkedListNode<(K, V)>> map;
public LRUCache(int cap) {
capacity = cap;
cache = new LinkedList<(K, V)>();
map = new Dictionary<K, LinkedListNode<(K, V)>>();
}
public V Get(K key) {
if (!map.ContainsKey(key)) return default(V);
cache.Remove(map[key]);
cache.AddFirst(map[key]);
return map[key].Value.value;
}
public void Put(K key, V value) {
if (map.ContainsKey(key)) {
cache.Remove(map[key]);
map.Remove(key);
}
if (cache.Count == capacity) {
var last = cache.Last.Value;
map.Remove(last.key);
cache.RemoveLast();
}
var node = cache.AddFirst((key, value));
map[key] = node;
}
}
class Program {
static void Main() {
LRUCache<int, int> lru = new LRUCache<int, int>(2);
lru.Put(1, 10);
lru.Put(2, 20);
Console.WriteLine(lru.Get(1));
lru.Put(3, 30);
Console.WriteLine(lru.Get(2));
Console.WriteLine(lru.Get(3));
}
}Kahn's Algorithm uses an in-degree array and a Queue to produce a topological ordering of a Directed Acyclic Graph (DAG).
- Calculate in-degree for each vertex
- Start with zero in-degree vertices in queue
- Decrement in-degree of neighbors when processing
- If output size equals V, no cycle exists
// Graph - Topological Sort in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static List<int> TopoSort(int V, List<int>[] adj) {
int[] inDegree = new int[V];
for (int u = 0; u < V; u++)
foreach (int v in adj[u]) inDegree[v]++;
Queue<int> q = new Queue<int>();
for (int i = 0; i < V; i++)
if (inDegree[i] == 0) q.Enqueue(i);
List<int> order = new List<int>();
while (q.Count > 0) {
int u = q.Dequeue();
order.Add(u);
foreach (int v in adj[u])
if (--inDegree[v] == 0) q.Enqueue(v);
}
return order.Count == V ? order : new List<int>();
}
static void Main() {
int V = 6;
List<int>[] adj = new List<int>[V];
for (int i = 0; i < V; i++) adj[i] = new List<int>();
adj[5].Add(2); adj[5].Add(0);
adj[4].Add(0); adj[4].Add(1);
adj[2].Add(3); adj[3].Add(1);
var order = TopoSort(V, adj);
Console.WriteLine($"Topological Order: {string.Join(" ", order)}");
}
}Bit manipulation in C# uses the same operators as C. XOR for finding unique elements and Brian Kernighan's algorithm for bit counting are classic techniques.
- Operators:
&,|,^,~,<<,>> - XOR trick:
a ^ a = 0,a ^ 0 = a - Brian Kernighan:
n &= n - 1 - Power of 2 check:
(n & (n - 1)) == 0
// Bit Manipulation in C#
using System;
class Program {
static bool IsBitSet(int n, int p) => (n & (1 << p)) != 0;
static int SetBit(int n, int p) => n | (1 << p);
static int ClearBit(int n, int p) => n & ~(1 << p);
static int ToggleBit(int n, int p) => n ^ (1 << p);
static int CountBits(int n) {
int c = 0;
while (n > 0) { n &= n - 1; c++; }
return c;
}
static bool IsPowerOf2(int n) => n > 0 && (n & (n - 1)) == 0;
// Find unique (all others appear twice)
static int FindUnique(int[] arr) {
int res = 0;
foreach (int x in arr) res ^= x;
return res;
}
static void Main() {
int n = 0b10110100;
Console.WriteLine($"Number: {n}");
Console.WriteLine($"Bit 2 set? {IsBitSet(n, 2)}");
Console.WriteLine($"Set bit 0: {SetBit(n, 0)}");
Console.WriteLine($"Clear bit 4: {ClearBit(n, 4)}");
Console.WriteLine($"Toggle bit 7: {ToggleBit(n, 7)}");
Console.WriteLine($"Count bits: {CountBits(n)}");
Console.WriteLine($"isPow2(16): {IsPowerOf2(16)}");
int[] arr = { 2, 3, 5, 4, 5, 3, 4 };
Console.WriteLine($"Unique: {FindUnique(arr)}");
}
}C# efficiently implements number theory algorithms like GCD (Euclidean), prime sieve, and modular exponentiation using recursion and bitwise operations.
- GCD:
return b == 0 ? a : GCD(b, a % b) - Sieve of Eratosthenes: O(n log log n)
- Modular exponentiation: O(log n)
exp >>= 1for fast division
// Number Theory in C#
using System;
using System.Collections.Generic;
class Program {
static int GCD(int a, int b) => b == 0 ? a : GCD(b, a % b);
static int LCM(int a, int b) => a / GCD(a, b) * b;
static bool IsPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++)
if (n % i == 0) return false;
return true;
}
static List<int> Sieve(int limit) {
bool[] notPrime = new bool[limit + 1];
if (limit >= 0) notPrime[0] = true;
if (limit >= 1) notPrime[1] = true;
for (int i = 2; i * i <= limit; i++)
if (!notPrime[i])
for (int j = i * i; j <= limit; j += i)
notPrime[j] = true;
List<int> primes = new List<int>();
for (int i = 2; i <= limit; i++)
if (!notPrime[i]) primes.Add(i);
return primes;
}
static long ModPow(long baseNum, long exp, long mod) {
long result = 1;
baseNum %= mod;
while (exp > 0) {
if ((exp & 1) == 1) result = result * baseNum % mod;
baseNum = baseNum * baseNum % mod;
exp >>= 1;
}
return result;
}
static void Main() {
Console.WriteLine($"GCD(48,18)={GCD(48, 18)}");
Console.WriteLine($"LCM(4,6)={LCM(4, 6)}");
Console.WriteLine($"isPrime(17)={IsPrime(17)}");
var primes = Sieve(50);
Console.WriteLine($"Primes: {string.Join(" ", primes)}");
Console.WriteLine($"2^10 mod 1000 = {ModPow(2, 10, 1000)}");
}
}Tuple is a reference type with Item1, Item2 properties. ValueTuple (C# 7) is a value type with named fields and deconstruction support.
Tuple— reference type, heap allocatedValueTuple— value type, stack allocated- Named tuple:
(string name, int age) person - Deconstruction:
var (name, age) = person
// Tuple and ValueTuple in C#
using System;
using System.Collections.Generic;
class Program {
static void Main() {
// Tuple
Tuple<string, int, double> t1 = Tuple.Create("Alice", 25, 3.85);
Console.WriteLine($"{t1.Item1} age={t1.Item2} gpa={t1.Item3}");
// ValueTuple (C# 7)
(string name, int age, double gpa) t2 = ("Bob", 22, 3.62);
Console.WriteLine($"{t2.name} age={t2.age} gpa={t2.gpa}");
// Tuple deconstruction
(string n, int a, double g) = t2;
Console.WriteLine($"Deconstructed: {n}, {a}, {g}");
// Named tuple in method return
var result = GetMinMax(new List<int> { 5, 2, 8, 1, 9 });
Console.WriteLine($"Min={result.min}, Max={result.max}");
// Tuple in collections
List<(string, int)> students = new List<(string, int)> {
("Alice", 90), ("Bob", 85), ("Carol", 92)
};
students.Sort((a, b) => b.Item2.CompareTo(a.Item2));
foreach (var (name, score) in students)
Console.WriteLine($"{name}: {score}");
}
static (int min, int max) GetMinMax(List<int> nums) {
int min = int.MaxValue, max = int.MinValue;
foreach (int x in nums) {
if (x < min) min = x;
if (x > max) max = x;
}
return (min, max);
}
}The two pointers technique uses two index variables moving towards each other to solve array problems in O(n) time with O(1) space.
- Container with most water: maximize area between bars
- 3-Sum: fix one element, two-pointer the rest
- Skip duplicates for unique triplets
- Requires sorted input for most applications
// Two Pointers Technique in C#
using System;
using System.Collections.Generic;
class Program {
// Container with most water
static int MaxWater(int[] height) {
int l = 0, r = height.Length - 1, maxArea = 0;
while (l < r) {
maxArea = Math.Max(maxArea,
Math.Min(height[l], height[r]) * (r - l));
if (height[l] < height[r]) l++;
else r--;
}
return maxArea;
}
// 3-sum
static List<List<int>> ThreeSum(int[] nums) {
Array.Sort(nums);
List<List<int>> res = new List<List<int>>();
for (int i = 0; i < nums.Length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int l = i + 1, r = nums.Length - 1;
while (l < r) {
int sum = nums[i] + nums[l] + nums[r];
if (sum == 0) {
res.Add(new List<int> { nums[i], nums[l], nums[r] });
while (l < r && nums[l] == nums[l + 1]) l++;
while (l < r && nums[r] == nums[r - 1]) r--;
l++; r--;
} else if (sum < 0) l++;
else r--;
}
}
return res;
}
static void Main() {
int[] h = { 1, 8, 6, 2, 5, 4, 8, 3, 7 };
Console.WriteLine($"Max water: {MaxWater(h)}");
int[] nums = { -1, 0, 1, 2, -1, -4 };
foreach (var triplet in ThreeSum(nums))
Console.WriteLine(string.Join(" ", triplet));
}
}Backtracking systematically explores all possibilities by building candidates incrementally and abandoning those that fail constraints. Subsets and permutations are classic examples.
- Subsets: include/exclude each element
- Permutations: swap + recurse + swap back
List<T>withAdd()andRemoveAt()- 2ⁿ subsets, n! permutations for n elements
// Backtracking in C#
using System;
using System.Collections.Generic;
class Program {
// Generate all subsets
static List<List<int>> Subsets(int[] nums) {
List<List<int>> res = new List<List<int>>();
BacktrackSubsets(nums, 0, new List<int>(), res);
return res;
}
static void BacktrackSubsets(int[] nums, int idx,
List<int> curr, List<List<int>> res) {
res.Add(new List<int>(curr));
for (int i = idx; i < nums.Length; i++) {
curr.Add(nums[i]);
BacktrackSubsets(nums, i + 1, curr, res);
curr.RemoveAt(curr.Count - 1);
}
}
// Generate permutations
static List<List<int>> Permute(int[] nums) {
List<List<int>> res = new List<List<int>>();
PermuteHelper(nums, 0, res);
return res;
}
static void PermuteHelper(int[] nums, int start, List<List<int>> res) {
if (start == nums.Length) {
res.Add(new List<int>(nums));
return;
}
for (int i = start; i < nums.Length; i++) {
Swap(nums, start, i);
PermuteHelper(nums, start + 1, res);
Swap(nums, start, i);
}
}
static void Swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void Main() {
int[] nums = { 1, 2, 3 };
var subsets = Subsets(nums);
Console.WriteLine($"Subsets ({subsets.Count}):");
foreach (var s in subsets)
Console.WriteLine($"[{string.Join(" ", s)}]");
var perms = Permute(nums);
Console.WriteLine($"Permutations ({perms.Count}):");
foreach (var p in perms)
Console.WriteLine(string.Join(" ", p));
}
}Greedy algorithms make locally optimal choices. Activity Selection and Fractional Knapsack are classic examples that work with greedy strategy.
- Activity Selection: sort by end time
- Fractional Knapsack: sort by value/weight ratio
Array.Sort()with custom comparer- Tuple sorting with
OrderBy
// Greedy Algorithms in C#
using System;
using System.Collections.Generic;
class Program {
// Activity Selection
static int ActivitySelection((int start, int end)[] activities) {
Array.Sort(activities, (a, b) => a.end.CompareTo(b.end));
int count = 1, lastEnd = activities[0].end;
for (int i = 1; i < activities.Length; i++) {
if (activities[i].start >= lastEnd) {
count++;
lastEnd = activities[i].end;
}
}
return count;
}
// Fractional Knapsack
static double FractionalKnapsack((int value, int weight)[] items, int W) {
Array.Sort(items, (a, b) =>
((double)b.value / b.weight).CompareTo((double)a.value / a.weight));
double total = 0;
foreach (var (val, wt) in items) {
if (W >= wt) { total += val; W -= wt; }
else { total += (double)val / wt * W; break; }
}
return total;
}
static void Main() {
(int, int)[] acts = {
(1, 3), (2, 5), (4, 6), (6, 8), (5, 7)
};
Console.WriteLine($"Max activities: {ActivitySelection(acts)}");
(int, int)[] items = {
(60, 10), (100, 20), (120, 30)
};
Console.WriteLine($"Max value (W=50): {FractionalKnapsack(items, 50)}");
}
}Delegates are type-safe function pointers. Events provide a publish/subscribe pattern using delegates. They are used extensively in GUI programming and the Observer pattern.
delegate— type-safe callbackevent— encapsulated multicast delegateEventHandlerandEventArgs— standard pattern?.Invoke()— safe event invocation
// Events and Delegates in C#
using System;
// Delegate definition
public delegate void PriceChangedEventHandler(object sender, PriceChangedEventArgs e);
// Event args
public class PriceChangedEventArgs : EventArgs {
public decimal OldPrice { get; }
public decimal NewPrice { get; }
public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
OldPrice = oldPrice;
NewPrice = newPrice;
}
}
// Subject
class Stock {
private string symbol;
private decimal price;
public event PriceChangedEventHandler PriceChanged;
public Stock(string symbol, decimal price) {
this.symbol = symbol;
this.price = price;
}
public decimal Price {
get { return price; }
set {
if (price == value) return;
decimal old = price;
price = value;
OnPriceChanged(old, price);
}
}
protected virtual void OnPriceChanged(decimal oldPrice, decimal newPrice) {
PriceChanged?.Invoke(this, new PriceChangedEventArgs(oldPrice, newPrice));
}
}
// Observer
class Investor {
private string name;
public Investor(string name) {
this.name = name;
}
public void HandlePriceChange(object sender, PriceChangedEventArgs e) {
Console.WriteLine(name + " notified: Price changed from $" + e.OldPrice + " to $" + e.NewPrice);
}
}
class Program {
static void Main() {
Stock apple = new Stock("AAPL", 150);
Investor alice = new Investor("Alice");
Investor bob = new Investor("Bob");
apple.PriceChanged += alice.HandlePriceChange;
apple.PriceChanged += bob.HandlePriceChange;
apple.Price = 155;
apple.Price = 160;
}
}IDisposable provides deterministic resource cleanup. The using statement ensures Dispose() is called even with exceptions, following the RAII pattern.
IDisposable— implementDispose()usingstatement — auto-disposeusingdeclaration (C# 8+) — scoped disposal- Used for file handles, database connections, mutexes
// IDisposable and Resource Management
using System;
using System.IO;
class FileHandler : IDisposable {
private StreamWriter writer;
private string filename;
public FileHandler(string name) {
filename = name;
writer = new StreamWriter(name);
Console.WriteLine($"File opened: {name}");
}
public void Write(string data) {
writer.WriteLine(data);
}
public void Dispose() {
if (writer != null) {
writer.Close();
writer.Dispose();
Console.WriteLine($"File closed: {filename}");
}
}
}
class Program {
static void Main() {
// Using statement - auto dispose
using (FileHandler fh = new FileHandler("test.txt")) {
fh.Write("Hello RAII!");
fh.Write("Line 2");
} // Auto-disposed here
// Using declaration (C# 8+)
using FileHandler fh2 = new FileHandler("test2.txt");
fh2.Write("Data");
// Auto-disposed at end of scope
// Manual disposal with try-finally
FileHandler fh3 = null;
try {
fh3 = new FileHandler("test3.txt");
fh3.Write("Data");
} finally {
fh3?.Dispose();
}
}
}C# supports async/await for asynchronous I/O, Parallel.For for CPU-bound work, and PLINQ for parallel data processing. CancellationToken enables graceful cancellation.
async/await— non-blocking I/OParallel.For— parallel loops- PLINQ:
AsParallel()for parallel LINQ CancellationToken— cancellation support
// Async and Parallel Programming
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
class Program {
static async Task Main() {
// Parallel.For
Console.WriteLine("Parallel.For:");
Parallel.For(0, 10, i => {
Thread.Sleep(100);
Console.WriteLine($"Task {i} on thread {Thread.CurrentThread.ManagedThreadId}");
});
// Parallel.ForEach
Console.WriteLine("
Parallel.ForEach:");
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Parallel.ForEach(numbers, n => {
Console.WriteLine($"Square of {n} = {n * n} on thread {Thread.CurrentThread.ManagedThreadId}");
});
// PLINQ
Console.WriteLine("
PLINQ:");
var squares = numbers.AsParallel()
.Select(n => n * n)
.ToList();
Console.WriteLine($"Squares: {string.Join(", ", squares)}");
// Async tasks
var tasks = new List<Task<int>>();
for (int i = 0; i < 5; i++) {
int num = i;
tasks.Add(Task.Run(() => {
Thread.Sleep(100);
return num * num;
}));
}
int[] results = await Task.WhenAll(tasks);
Console.WriteLine($"Async results: {string.Join(", ", results)}");
// Cancellation token
var cts = new CancellationTokenSource();
cts.CancelAfter(500);
try {
await Task.Run(() => {
for (int i = 0; i < 10; i++) {
cts.Token.ThrowIfCancellationRequested();
Thread.Sleep(100);
Console.WriteLine($"Working... {i}");
}
}, cts.Token);
} catch (OperationCanceledException) {
Console.WriteLine("Cancelled!");
}
}
}C#'s Regex class supports powerful pattern matching. Regex.Match() finds matches, Regex.Replace() replaces, and named groups make patterns readable.
Regex.IsMatch()— check patternRegex.Matches()— find all matchesRegex.Replace()— replace matches- Named groups:
(?<name>pattern)
// Regular Expressions in C#
using System;
using System.Text.RegularExpressions;
class Program {
static void Main() {
// Email validation
Regex emailRx = new Regex(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$");
string[] emails = { "user@example.com", "invalid-email", "hello@world.org" };
foreach (string e in emails)
Console.WriteLine($"{e}: {(emailRx.IsMatch(e) ? "Valid" : "Invalid")}");
// Search and replace
string text = "The quick brown fox jumps over the lazy dog";
string replaced = Regex.Replace(text, @"w{4}", "****");
Console.WriteLine($"Replaced: {replaced}");
// Find all matches
string data = "Price: $100, Discount: $20, Total: $80";
Regex numRx = new Regex(@"$(d+)");
MatchCollection matches = numRx.Matches(data);
Console.Write("Numbers found: ");
foreach (Match m in matches)
Console.Write($"{m.Groups[1].Value} ");
Console.WriteLine();
// Named groups
Regex dateRx = new Regex(@"(?<year>d{4})-(?<month>d{2})-(?<day>d{2})");
Match dateMatch = dateRx.Match("2024-01-15");
if (dateMatch.Success) {
Console.WriteLine($"Year: {dateMatch.Groups["year"]}");
Console.WriteLine($"Month: {dateMatch.Groups["month"]}");
Console.WriteLine($"Day: {dateMatch.Groups["day"]}");
}
// Capture groups
Regex wordRx = new Regex(@"(w+)s+(w+)");
string sentence = "Hello World";
Match match = wordRx.Match(sentence);
if (match.Success) {
Console.WriteLine($"Word 1: {match.Groups[1]}");
Console.WriteLine($"Word 2: {match.Groups[2]}");
}
}
}Attributes add metadata to code. Reflection allows inspecting and invoking types at runtime, enabling dynamic behavior and frameworks like dependency injection.
Attribute— custom metadataType— runtime type informationMethodInfo.Invoke()— dynamic method invocation- Used in serialization, ORM, and testing frameworks
// Attributes and Reflection in C#
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
class AuthorAttribute : Attribute {
public string Name { get; }
public string Version { get; set; }
public AuthorAttribute(string name) {
Name = name;
Version = "1.0";
}
}
[Author("John Doe")]
class Calculator {
[Author("Jane Smith", Version = "2.0")]
public int Add(int a, int b) {
return a + b;
}
public int Multiply(int a, int b) {
return a * b;
}
}
class Program {
static void Main() {
// Get class attributes
Type calcType = typeof(Calculator);
AuthorAttribute classAttr = (AuthorAttribute)Attribute
.GetCustomAttribute(calcType, typeof(AuthorAttribute));
if (classAttr != null) {
Console.WriteLine($"Class Author: {classAttr.Name} (v{classAttr.Version})");
}
// Get method attributes
MethodInfo method = calcType.GetMethod("Add");
AuthorAttribute methodAttr = (AuthorAttribute)Attribute
.GetCustomAttribute(method, typeof(AuthorAttribute));
if (methodAttr != null) {
Console.WriteLine($"Method Author: {methodAttr.Name} (v{methodAttr.Version})");
}
// Reflection - invoke methods
Calculator calc = new Calculator();
MethodInfo addMethod = calcType.GetMethod("Add");
int result = (int)addMethod.Invoke(calc, new object[] { 5, 3 });
Console.WriteLine($"Add(5,3) = {result}");
// Get all methods
Console.WriteLine("
All methods:");
foreach (var m in calcType.GetMethods()) {
Console.WriteLine($" {m.Name}");
}
// Dynamic invocation
MethodInfo multMethod = calcType.GetMethod("Multiply");
int multResult = (int)multMethod.Invoke(calc, new object[] { 4, 5 });
Console.WriteLine($"Multiply(4,5) = {multResult}");
}
}The Observer pattern uses events/delegates for one-to-many notification. The Command pattern encapsulates operations as objects, enabling undo/redo functionality.
- Observer: events and delegates
- Command:
Execute()andUndo() - History for undo support
- Widely used in GUI and game development
// Design Patterns - Observer and Command
using System;
using System.Collections.Generic;
// Observer Pattern
interface IObserver {
void Update(string eventName, int data);
}
class Subject {
private List<IObserver> observers = new List<IObserver>();
private int data;
public void Subscribe(IObserver o) {
observers.Add(o);
}
public void Unsubscribe(IObserver o) {
observers.Remove(o);
}
public int Data {
get { return data; }
set {
data = value;
Notify();
}
}
private void Notify() {
foreach (var o in observers)
o.Update("Data Changed", data);
}
}
class ConsoleObserver : IObserver {
private string name;
public ConsoleObserver(string name) {
this.name = name;
}
public void Update(string eventName, int data) {
Console.WriteLine($"{name} notified: {eventName} = {data}");
}
}
// Command Pattern
interface ICommand {
void Execute();
void Undo();
}
class Counter {
private int value = 0;
public void Increment(int n) { value += n; }
public void Decrement(int n) { value -= n; }
public int Value => value;
}
class IncrementCommand : ICommand {
private Counter counter;
private int amount;
public IncrementCommand(Counter c, int a) {
counter = c;
amount = a;
}
public void Execute() { counter.Increment(amount); }
public void Undo() { counter.Decrement(amount); }
}
class Program {
static void Main() {
// Observer
Subject subject = new Subject();
subject.Subscribe(new ConsoleObserver("Observer1"));
subject.Subscribe(new ConsoleObserver("Observer2"));
subject.Data = 42;
subject.Data = 100;
// Command
Counter counter = new Counter();
List<ICommand> history = new List<ICommand>();
history.Add(new IncrementCommand(counter, 10));
history.Add(new IncrementCommand(counter, 5));
foreach (var cmd in history) cmd.Execute();
Console.WriteLine($"Counter: {counter.Value}");
history[history.Count - 1].Undo();
Console.WriteLine($"After undo: {counter.Value}");
}
}C# supports functional programming with lambdas, higher-order functions, and LINQ. Func and Action delegates enable map, filter, and reduce operations.
Select()— map over collectionWhere()— filter predicateAggregate()— reduce/foldMemoizewithDictionarycache
// Functional Programming in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
// Higher-order functions
static List<T> Map<T>(List<T> list, Func<T, T> fn) {
return list.Select(fn).ToList();
}
static List<T> Filter<T>(List<T> list, Func<T, bool> pred) {
return list.Where(pred).ToList();
}
static R Reduce<T, R>(List<T> list, R init, Func<R, T, R> fn) {
return list.Aggregate(init, fn);
}
// Function composition
static Func<T, R> Compose<T, U, R>(Func<U, R> f, Func<T, U> g) {
return x => f(g(x));
}
// Memoization
static Func<T, R> Memoize<T, R>(Func<T, R> fn) {
var cache = new Dictionary<T, R>();
return x => {
if (!cache.ContainsKey(x)) cache[x] = fn(x);
return cache[x];
};
}
static void Main() {
List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var doubled = Map(nums, x => x * 2);
Console.WriteLine($"Doubled: {string.Join(" ", doubled)}");
var evens = Filter(nums, x => x % 2 == 0);
Console.WriteLine($"Evens: {string.Join(" ", evens)}");
int sum = Reduce(nums, 0, (a, b) => a + b);
Console.WriteLine($"Sum: {sum}");
Func<int, int> addOne = x => x + 1;
Func<int, int> doubleIt = x => x * 2;
var addThenDouble = Compose(doubleIt, addOne);
Console.WriteLine($"Compose(double, +1)(5) = {addThenDouble(5)}");
// Memoized Fibonacci
Func<int, int> fib = null;
fib = n => n <= 1 ? n : fib(n - 1) + fib(n - 2);
var memoFib = Memoize(fib);
Console.WriteLine($"fib(30) = {memoFib(30)}");
}
}Generic constraints (where T : class, new()) restrict the types that can be used as generic arguments. This enables safe and efficient generic code.
classconstraint — reference typesstructconstraint — value typesnew()constraint — parameterless constructornotnullconstraint (C# 8+) — non-nullable types
// Advanced Generics and Constraints in C#
using System;
using System.Collections.Generic;
// Generic constraints
class Repository<T> where T : class, new() {
private List<T> items = new List<T>();
public void Add(T item) {
items.Add(item);
}
public T Create() {
return new T();
}
public List<T> GetAll() {
return items;
}
}
class Entity {
public int Id { get; set; }
public string Name { get; set; }
}
interface IRepository<T> where T : class {
void Add(T item);
T Get(int id);
}
class GenericRepository<T> : IRepository<T> where T : class, new() {
private Dictionary<int, T> items = new Dictionary<int, T>();
private int nextId = 1;
public void Add(T item) {
items[nextId++] = item;
}
public T Get(int id) {
return items.ContainsKey(id) ? items[id] : null;
}
}
class Program {
static void Main() {
Repository<Entity> repo = new Repository<Entity>();
Entity e1 = repo.Create();
e1.Name = "Alice";
repo.Add(e1);
Entity e2 = repo.Create();
e2.Name = "Bob";
repo.Add(e2);
foreach (var e in repo.GetAll()) {
Console.WriteLine(e.Name);
}
GenericRepository<Entity> genericRepo = new GenericRepository<Entity>();
genericRepo.Add(new Entity { Name = "Carol" });
Entity found = genericRepo.Get(1);
Console.WriteLine($"Found: {found?.Name}");
}
}Using 2D arrays (int[,]) makes matrix operations clean. Multiplication, transpose, and rotation are fundamental linear algebra operations.
- Matrix multiplication: O(r*k*c)
- Transpose: swap rows and columns
- 90° CW rotation: transpose then reverse each row
Tupleswap for clean code
// Matrix Operations in C#
using System;
class Program {
static int[,] Multiply(int[,] A, int[,] B) {
int r = A.GetLength(0), c = B.GetLength(1), k = A.GetLength(1);
int[,] C = new int[r, c];
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
for (int p = 0; p < k; p++)
C[i, j] += A[i, p] * B[p, j];
return C;
}
static int[,] Transpose(int[,] A) {
int r = A.GetLength(0), c = A.GetLength(1);
int[,] T = new int[c, r];
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
T[j, i] = A[i, j];
return T;
}
static void Rotate90(int[,] M) {
int n = M.GetLength(0);
// Transpose
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
(M[i, j], M[j, i]) = (M[j, i], M[i, j]);
// Reverse each row
for (int i = 0; i < n; i++)
for (int j = 0; j < n / 2; j++)
(M[i, j], M[i, n - 1 - j]) = (M[i, n - 1 - j], M[i, j]);
}
static void Print(int[,] M) {
int r = M.GetLength(0), c = M.GetLength(1);
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++)
Console.Write($"{M[i, j]} ");
Console.WriteLine();
}
}
static void Main() {
int[,] A = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int[,] B = { { 9, 8, 7 }, { 6, 5, 4 }, { 3, 2, 1 } };
Console.WriteLine("A*B:");
Print(Multiply(A, B));
Console.WriteLine("T(A):");
Print(Transpose(A));
Rotate90(A);
Console.WriteLine("A rotated 90CW:");
Print(A);
}
}The two pointer approach solves Trapping Rain Water in O(n) time and O(1) space by tracking left and right max water levels.
- Move the side with smaller height inward
- Track max height seen from each side
- Time O(n), Space O(1)
- Ternary operator for compact logic
// Trapping Rain Water in C#
using System;
class Program {
static int Trap(int[] height) {
int l = 0, r = height.Length - 1;
int leftMax = 0, rightMax = 0, water = 0;
while (l < r) {
if (height[l] < height[r]) {
if (height[l] >= leftMax) leftMax = height[l];
else water += leftMax - height[l];
l++;
} else {
if (height[r] >= rightMax) rightMax = height[r];
else water += rightMax - height[r];
r--;
}
}
return water;
}
static void Main() {
int[] h1 = { 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 };
Console.WriteLine($"Water trapped: {Trap(h1)}");
int[] h2 = { 4, 2, 0, 3, 2, 5 };
Console.WriteLine($"Water trapped: {Trap(h2)}");
}
}The O(n log n) LIS uses List.BinarySearch() to maintain a sorted tails array. The DP approach is O(n²) but easier to understand.
- DP:
dp[i]= LIS ending at index i - Binary search:
BinarySearch()replaces in tails - Tails length = LIS length
- O(n log n) time, O(n) space
// Longest Increasing Subsequence in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
// DP O(n^2)
static int LIS_DP(int[] arr) {
int n = arr.Length;
int[] dp = Enumerable.Repeat(1, n).ToArray();
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
if (arr[j] < arr[i])
dp[i] = Math.Max(dp[i], dp[j] + 1);
return dp.Max();
}
// Binary Search O(n log n)
static int LIS_BS(int[] arr) {
List<int> tails = new List<int>();
foreach (int x in arr) {
int idx = tails.BinarySearch(x);
if (idx < 0) idx = ~idx;
if (idx == tails.Count) tails.Add(x);
else tails[idx] = x;
}
return tails.Count;
}
static void Main() {
int[] arr = { 10, 9, 2, 5, 3, 7, 101, 18 };
Console.WriteLine($"LIS (DP): {LIS_DP(arr)}");
Console.WriteLine($"LIS (BS): {LIS_BS(arr)}");
}
}Bellman-Ford in C# uses structs for edges. It handles negative weight edges and detects negative weight cycles after V-1 relaxations.
- Relax all edges V-1 times
- V-th relaxation detects negative cycle
- Struct for clean edge representation
- Time O(V*E), works with negative weights
// Bellman-Ford in C#
using System;
using System.Collections.Generic;
class Program {
struct Edge {
public int u, v, w;
public Edge(int u, int v, int w) {
this.u = u;
this.v = v;
this.w = w;
}
}
static void BellmanFord(List<Edge> edges, int V, int src) {
int[] dist = new int[V];
Array.Fill(dist, int.MaxValue);
dist[src] = 0;
for (int i = 1; i < V; i++)
foreach (var e in edges)
if (dist[e.u] != int.MaxValue && dist[e.u] + e.w < dist[e.v])
dist[e.v] = dist[e.u] + e.w;
// Check negative cycle
foreach (var e in edges)
if (dist[e.u] != int.MaxValue && dist[e.u] + e.w < dist[e.v]) {
Console.WriteLine("Negative cycle detected!");
return;
}
Console.WriteLine($"Distances from {src}:");
for (int i = 0; i < V; i++)
Console.WriteLine($" {i}: {dist[i]}");
}
static void Main() {
int V = 5;
List<Edge> edges = new List<Edge> {
new Edge(0, 1, -1), new Edge(0, 2, 4),
new Edge(1, 2, 3), new Edge(1, 3, 2),
new Edge(1, 4, 2), new Edge(3, 2, 5),
new Edge(3, 1, 1), new Edge(4, 3, -3)
};
BellmanFord(edges, V, 0);
}
}Floyd-Warshall computes all-pairs shortest paths in O(V³). C# 2D arrays (int[,]) make the implementation clean with Math.Min() for relaxation.
- Triple nested loop: k, i, j
- Check overflow before relaxing
- Time O(V³), Space O(V²)
- Handles negative weights but not negative cycles
// Floyd-Warshall in C#
using System;
class Program {
static void FloydWarshall(int[,] dist) {
int V = dist.GetLength(0);
for (int k = 0; k < V; k++)
for (int i = 0; i < V; i++)
for (int j = 0; j < V; j++)
if (dist[i, k] != int.MaxValue && dist[k, j] != int.MaxValue)
dist[i, j] = Math.Min(dist[i, j],
dist[i, k] + dist[k, j]);
Console.WriteLine("All-Pairs Shortest Paths:");
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++)
Console.Write(dist[i, j] == int.MaxValue ? "INF " : $"{dist[i, j],3} ");
Console.WriteLine();
}
}
static void Main() {
const int INF = int.MaxValue;
int[,] graph = {
{0, 3, INF, 7 },
{8, 0, 2, INF},
{5, INF, 0, 1 },
{2, INF, INF, 0 }
};
FloydWarshall(graph);
}
}Kruskal's Algorithm with a DSU class and edge sorting using Sort() produces clean, readable C# code for Minimum Spanning Tree.
- Sort edges by weight with
Sort() - DSU class with path compression
Find()andUnite()methods- Time O(E log E) dominated by sorting
// Kruskal's MST in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
struct Edge {
public int u, v, w;
public Edge(int u, int v, int w) {
this.u = u; this.v = v; this.w = w;
}
}
class DSU {
private int[] parent, rank;
public DSU(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
}
public int Find(int x) {
if (parent[x] != x) parent[x] = Find(parent[x]);
return parent[x];
}
public bool Unite(int x, int y) {
int px = Find(x), py = Find(y);
if (px == py) return false;
if (rank[px] < rank[py]) (px, py) = (py, px);
parent[py] = px;
if (rank[px] == rank[py]) rank[px]++;
return true;
}
}
static void Main() {
int V = 4;
List<Edge> edges = new List<Edge> {
new Edge(0, 1, 10), new Edge(0, 2, 6),
new Edge(0, 3, 5), new Edge(1, 3, 15),
new Edge(2, 3, 4)
};
edges.Sort((a, b) => a.w.CompareTo(b.w));
DSU dsu = new DSU(V);
int cost = 0;
Console.WriteLine("MST Edges:");
foreach (var e in edges)
if (dsu.Unite(e.u, e.v)) {
Console.WriteLine($"{e.u} -- {e.v} (weight {e.w})");
cost += e.w;
}
Console.WriteLine($"MST Cost: {cost}");
}
}C# strings support expand-around-center for O(n) longest palindrome, frequency map for anagram check, and sorted key grouping for group anagrams.
- Palindrome: expand from each center
- Anagram:
Dictionaryfrequency check - Group Anagrams: sorted string as
Dictionarykey - All O(n) or O(n * k log k) time
// String Algorithms in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
// Longest palindromic substring
static string LongestPalindrome(string s) {
int n = s.Length, start = 0, maxLen = 1;
void Expand(int l, int r) {
while (l >= 0 && r < n && s[l] == s[r]) { l--; r++; }
if (r - l - 1 > maxLen) {
maxLen = r - l - 1;
start = l + 1;
}
}
for (int i = 0; i < n; i++) {
Expand(i, i);
Expand(i, i + 1);
}
return s.Substring(start, maxLen);
}
// Check anagram
static bool IsAnagram(string s1, string s2) {
if (s1.Length != s2.Length) return false;
var freq = new Dictionary<char, int>();
foreach (char c in s1) freq[c] = freq.GetValueOrDefault(c, 0) + 1;
foreach (char c in s2) {
if (!freq.ContainsKey(c)) return false;
if (--freq[c] < 0) return false;
}
return true;
}
// Group anagrams
static List<List<string>> GroupAnagrams(string[] words) {
var map = new Dictionary<string, List<string>>();
foreach (string w in words) {
char[] arr = w.ToCharArray();
Array.Sort(arr);
string key = new string(arr);
if (!map.ContainsKey(key)) map[key] = new List<string>();
map[key].Add(w);
}
return map.Values.ToList();
}
static void Main() {
Console.WriteLine(LongestPalindrome("babad"));
Console.WriteLine(IsAnagram("listen", "silent"));
string[] words = { "eat", "tea", "tan", "ate", "nat", "bat" };
foreach (var group in GroupAnagrams(words))
Console.WriteLine(string.Join(" ", group));
}
}C# arrays make DP table initialization and traversal clean. Coin Change (min coins), Count Ways, and Subset Sum are solved with bottom-up DP.
- Coin Change:
Math.Min()for optimization - Count Ways: unbounded knapsack variant
- Subset Sum: 0/1 knapsack with bool table
- All O(n * amount) time
// Coin Change and Subset Sum in C#
using System;
using System.Collections.Generic;
class Program {
// Minimum coins
static int CoinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Array.Fill(dp, int.MaxValue);
dp[0] = 0;
for (int i = 1; i <= amount; i++)
foreach (int c in coins)
if (c <= i && dp[i - c] != int.MaxValue)
dp[i] = Math.Min(dp[i], dp[i - c] + 1);
return dp[amount] == int.MaxValue ? -1 : dp[amount];
}
// Count ways
static int CountWays(int[] coins, int amount) {
int[] dp = new int[amount + 1];
dp[0] = 1;
foreach (int c in coins)
for (int i = c; i <= amount; i++)
dp[i] += dp[i - c];
return dp[amount];
}
// Subset sum
static bool SubsetSum(int[] arr, int target) {
int n = arr.Length;
bool[,] dp = new bool[n + 1, target + 1];
for (int i = 0; i <= n; i++) dp[i, 0] = true;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= target; j++) {
dp[i, j] = dp[i - 1, j];
if (arr[i - 1] <= j) dp[i, j] = dp[i, j] || dp[i - 1, j - arr[i - 1]];
}
return dp[n, target];
}
static void Main() {
int[] coins = { 1, 5, 6, 9 };
Console.WriteLine($"Min coins for 11: {CoinChange(coins, 11)}");
Console.WriteLine($"Ways for 10: {CountWays(coins, 10)}");
int[] arr = { 3, 34, 4, 12, 5, 2 };
Console.WriteLine($"Subset sum 9: {SubsetSum(arr, 9)}");
Console.WriteLine($"Subset sum 30: {SubsetSum(arr, 30)}");
}
}A Monotonic Stack maintains elements in increasing or decreasing order, enabling O(n) solutions for Next Greater Element and Largest Rectangle in Histogram.
- Pop elements violating monotonic property
- Next Greater: decreasing stack
- Histogram: pop and calculate area when height decreases
- Both O(n) time, O(n) space
// Monotonic Stack Problems
using System;
using System.Collections.Generic;
class Program {
// Next Greater Element
static int[] NextGreater(int[] arr) {
int n = arr.Length;
int[] res = new int[n];
Array.Fill(res, -1);
Stack<int> st = new Stack<int>();
for (int i = 0; i < n; i++) {
while (st.Count > 0 && arr[st.Peek()] < arr[i]) {
res[st.Pop()] = arr[i];
}
st.Push(i);
}
return res;
}
// Largest Rectangle in Histogram
static int LargestRect(int[] heights) {
Stack<int> st = new Stack<int>();
int maxArea = 0;
List<int> h = new List<int>(heights) { 0 };
for (int i = 0; i < h.Count; i++) {
while (st.Count > 0 && h[st.Peek()] > h[i]) {
int height = h[st.Pop()];
int width = st.Count == 0 ? i : i - st.Peek() - 1;
maxArea = Math.Max(maxArea, height * width);
}
st.Push(i);
}
return maxArea;
}
static void Main() {
int[] arr = { 4, 5, 2, 10, 8 };
var ng = NextGreater(arr);
Console.WriteLine($"Next Greater: {string.Join(" ", ng)}");
int[] h = { 2, 1, 5, 6, 2, 3 };
Console.WriteLine($"Largest Rect: {LargestRect(h)}");
}
}Beyond basic binary search, C# enables elegant solutions for rotated sorted arrays, peak elements, and first/last positions using Array.IndexOf() and Array.LastIndexOf().
- Rotated: determine which half is sorted
- Peak: move toward the rising side
Array.IndexOf()— first occurrenceArray.LastIndexOf()— last occurrence
// Binary Search Variants in C#
using System;
using System.Collections.Generic;
class Program {
// Search in rotated sorted array
static int SearchRotated(int[] arr, int target) {
int l = 0, r = arr.Length - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (arr[mid] == target) return mid;
if (arr[l] <= arr[mid]) {
if (target >= arr[l] && target < arr[mid]) r = mid - 1;
else l = mid + 1;
} else {
if (target > arr[mid] && target <= arr[r]) l = mid + 1;
else r = mid - 1;
}
}
return -1;
}
// Find peak element
static int FindPeak(int[] arr) {
int l = 0, r = arr.Length - 1;
while (l < r) {
int mid = (l + r) / 2;
if (arr[mid] > arr[mid + 1]) r = mid;
else l = mid + 1;
}
return l;
}
// First and last position
static (int first, int last) FirstLast(int[] arr, int target) {
int first = Array.IndexOf(arr, target);
if (first == -1) return (-1, -1);
int last = Array.LastIndexOf(arr, target);
return (first, last);
}
static void Main() {
int[] rotated = { 4, 5, 6, 7, 0, 1, 2 };
Console.WriteLine($"Search 0: {SearchRotated(rotated, 0)}");
int[] arr = { 1, 2, 3, 1 };
Console.WriteLine($"Peak index: {FindPeak(arr)}");
int[] v = { 5, 7, 7, 8, 8, 10 };
var (f, l) = FirstLast(v, 8);
Console.WriteLine($"First,Last of 8: {f},{l}");
}
}A two-pass left/right product approach achieves O(n) time and O(1) extra space. Array.Fill() initializes the result array with 1s.
- Left pass: prefix products into result
- Right pass: multiply suffix product
- Time O(n), Space O(1) extra
- No division needed — handles zeros
// Product of Array Except Self in C#
using System;
using System.Collections.Generic;
class Program {
static int[] ProductExceptSelf(int[] nums) {
int n = nums.Length;
int[] result = new int[n];
Array.Fill(result, 1);
// Left pass
for (int i = 1; i < n; i++)
result[i] = result[i - 1] * nums[i - 1];
// Right pass
int right = 1;
for (int i = n - 1; i >= 0; i--) {
result[i] *= right;
right *= nums[i];
}
return result;
}
static void Main() {
int[] nums = { 1, 2, 3, 4 };
var res = ProductExceptSelf(nums);
Console.WriteLine($"Output: {string.Join(" ", res)}");
}
}Flood Fill changes all connected same-color pixels. Number of Islands counts connected groups of '1's. Both use DFS with 4-directional traversal.
- Mark visited cells to avoid reprocessing
- 4-directional: up, down, left, right
- Time O(R*C), Space O(R*C) recursion stack
- Jagged arrays (
char[][]) for grid
// Flood Fill and Number of Islands
using System;
using System.Collections.Generic;
class Program {
// Flood Fill
static void FloodFill(int[][] img, int r, int c, int oldColor, int newColor) {
if (r < 0 || r >= img.Length || c < 0 || c >= img[0].Length) return;
if (img[r][c] != oldColor || img[r][c] == newColor) return;
img[r][c] = newColor;
FloodFill(img, r + 1, c, oldColor, newColor);
FloodFill(img, r - 1, c, oldColor, newColor);
FloodFill(img, r, c + 1, oldColor, newColor);
FloodFill(img, r, c - 1, oldColor, newColor);
}
// Number of Islands
static void DFS(char[][] grid, int r, int c) {
if (r < 0 || r >= grid.Length || c < 0 || c >= grid[0].Length || grid[r][c] == '0') return;
grid[r][c] = '0';
DFS(grid, r + 1, c);
DFS(grid, r - 1, c);
DFS(grid, r, c + 1);
DFS(grid, r, c - 1);
}
static int NumIslands(char[][] grid) {
int count = 0;
for (int r = 0; r < grid.Length; r++)
for (int c = 0; c < grid[0].Length; c++)
if (grid[r][c] == '1') {
DFS(grid, r, c);
count++;
}
return count;
}
static void Main() {
char[][] grid = {
new char[] { '1', '1', '0', '0' },
new char[] { '1', '1', '0', '0' },
new char[] { '0', '0', '1', '0' },
new char[] { '0', '0', '0', '1' }
};
Console.WriteLine($"Islands: {NumIslands(grid)}");
}
}Word Search uses DFS with backtracking. Mark a cell as visited by replacing with '#', recurse in all 4 directions, then restore the cell.
- Mark cell '#' to prevent reuse
- Restore cell after DFS
- Return true as soon as word is found
- Time O(R*C*4^L) where L is word length
// Word Search in Grid
using System;
using System.Collections.Generic;
class Program {
static bool DFS(char[][] board, string word, int r, int c, int idx) {
if (idx == word.Length) return true;
if (r < 0 || r >= board.Length || c < 0 || c >= board[0].Length ||
board[r][c] != word[idx]) return false;
char tmp = board[r][c];
board[r][c] = '#';
bool found = DFS(board, word, r + 1, c, idx + 1) ||
DFS(board, word, r - 1, c, idx + 1) ||
DFS(board, word, r, c + 1, idx + 1) ||
DFS(board, word, r, c - 1, idx + 1);
board[r][c] = tmp;
return found;
}
static bool WordSearch(char[][] board, string word) {
for (int r = 0; r < board.Length; r++)
for (int c = 0; c < board[0].Length; c++)
if (DFS(board, word, r, c, 0)) return true;
return false;
}
static void Main() {
char[][] board = {
new char[] { 'A', 'B', 'C', 'E' },
new char[] { 'S', 'F', 'C', 'S' },
new char[] { 'A', 'D', 'E', 'E' }
};
Console.WriteLine(WordSearch(board, "ABCCED"));
Console.WriteLine(WordSearch(board, "SEE"));
Console.WriteLine(WordSearch(board, "ABCB"));
}
}Spiral Matrix Traversal uses four shrinking boundary pointers: top, bottom, left, right. Each pass around the boundary adds elements.
- Traverse: right → down → left → up
- Shrink boundaries after each direction
- Check boundaries before left/up traversal
- Time O(m*n), Space O(1) excluding result
// Spiral Matrix in C#
using System;
using System.Collections.Generic;
class Program {
static List<int> SpiralOrder(int[][] matrix) {
List<int> res = new List<int>();
int top = 0, bottom = matrix.Length - 1;
int left = 0, right = matrix[0].Length - 1;
while (top <= bottom && left <= right) {
for (int i = left; i <= right; i++) res.Add(matrix[top][i]);
top++;
for (int i = top; i <= bottom; i++) res.Add(matrix[i][right]);
right--;
if (top <= bottom) {
for (int i = right; i >= left; i--) res.Add(matrix[bottom][i]);
bottom--;
}
if (left <= right) {
for (int i = bottom; i >= top; i--) res.Add(matrix[i][left]);
left++;
}
}
return res;
}
static void Main() {
int[][] mat = {
new int[] { 1, 2, 3, 4 },
new int[] { 5, 6, 7, 8 },
new int[] { 9, 10, 11, 12 },
new int[] { 13, 14, 15, 16 }
};
Console.WriteLine($"Spiral: {string.Join(" ", SpiralOrder(mat))}");
}
}The Sudoku Solver uses backtracking. For each empty cell, try digits '1'–'9', check row/column/box validity, recurse, and backtrack.
- Validate row, column, and 3×3 box
- Box index:
3*(r/3)+i/3,3*(c/3)+i%3 - Return true immediately when all cells are filled
- Classic constraint satisfaction + backtracking
// Sudoku Solver in C#
using System;
class Program {
static bool IsValid(char[][] board, int r, int c, char num) {
for (int i = 0; i < 9; i++) {
if (board[r][i] == num) return false;
if (board[i][c] == num) return false;
if (board[3 * (r / 3) + i / 3][3 * (c / 3) + i % 3] == num) return false;
}
return true;
}
static bool Solve(char[][] board) {
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 9; c++) {
if (board[r][c] == '.') {
for (char num = '1'; num <= '9'; num++) {
if (IsValid(board, r, c, num)) {
board[r][c] = num;
if (Solve(board)) return true;
board[r][c] = '.';
}
}
return false;
}
}
}
return true;
}
static void Main() {
char[][] board = {
new char[] { '5', '3', '.', '.', '7', '.', '.', '.', '.' },
new char[] { '6', '.', '.', '1', '9', '5', '.', '.', '.' },
new char[] { '.', '9', '8', '.', '.', '.', '.', '6', '.' },
new char[] { '8', '.', '.', '.', '6', '.', '.', '.', '3' },
new char[] { '4', '.', '.', '8', '.', '3', '.', '.', '1' },
new char[] { '7', '.', '.', '.', '2', '.', '.', '.', '6' },
new char[] { '.', '6', '.', '.', '.', '.', '2', '8', '.' },
new char[] { '.', '.', '.', '4', '1', '9', '.', '.', '5' },
new char[] { '.', '.', '.', '.', '8', '.', '.', '7', '9' }
};
Solve(board);
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++)
Console.Write($"{board[i][j]} ");
Console.WriteLine();
}
}
}A custom IComparer enables priority queues to order complex objects by multiple criteria — priority first, then deadline, enabling sophisticated scheduling.
- Implement
IComparer<T> PriorityQueue<T, T>with custom comparer- Multi-level sorting: primary and tiebreaker
- Used in job scheduling, event simulation
// Priority Queue Custom Comparator
using System;
using System.Collections.Generic;
class Task {
public string Name { get; set; }
public int Priority { get; set; }
public int Deadline { get; set; }
}
class TaskComparer : IComparer<Task> {
public int Compare(Task a, Task b) {
if (a.Priority != b.Priority) return b.Priority.CompareTo(a.Priority);
return a.Deadline.CompareTo(b.Deadline);
}
}
class Program {
static void Main() {
var taskQueue = new PriorityQueue<Task, Task>(new TaskComparer());
taskQueue.Enqueue(new Task { Name = "Write Report", Priority = 3, Deadline = 5 });
taskQueue.Enqueue(new Task { Name = "Fix Bug", Priority = 5, Deadline = 2 });
taskQueue.Enqueue(new Task { Name = "Code Review", Priority = 4, Deadline = 3 });
taskQueue.Enqueue(new Task { Name = "Deploy Feature", Priority = 5, Deadline = 1 });
taskQueue.Enqueue(new Task { Name = "Write Tests", Priority = 3, Deadline = 4 });
Console.WriteLine("Task execution order:");
while (taskQueue.Count > 0) {
var t = taskQueue.Dequeue();
Console.WriteLine($" [P={t.Priority},D={t.Deadline}] {t.Name}");
}
}
}Prim's Algorithm uses a SortedSet as a min-heap. It grows the MST by picking the minimum weight edge connecting visited to unvisited vertices.
- Start from vertex 0 with cost 0
SortedSetas min-heap- Time O((V+E) log V) with priority queue
- Best for dense graphs vs. Kruskal's for sparse
// Graph - Prim's MST in C#
using System;
using System.Collections.Generic;
class Program {
static int PrimMST(List<(int, int)>[] graph, int V) {
int[] key = new int[V];
bool[] inMST = new bool[V];
Array.Fill(key, int.MaxValue);
var pq = new SortedSet<(int weight, int node)>();
key[0] = 0;
pq.Add((0, 0));
int totalCost = 0;
while (pq.Count > 0) {
var (wt, u) = pq.Min;
pq.Remove(pq.Min);
if (inMST[u]) continue;
inMST[u] = true;
totalCost += wt;
foreach (var (w, v) in graph[u]) {
if (!inMST[v] && w < key[v]) {
pq.Remove((key[v], v));
key[v] = w;
pq.Add((key[v], v));
}
}
}
return totalCost;
}
static void Main() {
int V = 5;
var graph = new List<(int, int)>[V];
for (int i = 0; i < V; i++) graph[i] = new List<(int, int)>();
void AddEdge(int u, int v, int w) {
graph[u].Add((w, v));
graph[v].Add((w, u));
}
AddEdge(0, 1, 2); AddEdge(0, 3, 6);
AddEdge(1, 2, 3); AddEdge(1, 3, 8);
AddEdge(1, 4, 5); AddEdge(2, 4, 7);
AddEdge(3, 4, 9);
Console.WriteLine($"MST Cost (Prim's): {PrimMST(graph, V)}");
}
}A custom iterator is created by implementing IEnumerable<T> and using yield return for lazy evaluation. This enables LINQ and range-based loops.
- Implement
IEnumerable<T> - Use
yield returnfor lazy iteration - Works seamlessly with LINQ and
foreach - Custom Fibonacci and Range generators
// Custom Iterator Pattern in C#
using System;
using System.Collections;
using System.Collections.Generic;
// Custom Range iterator
class Range : IEnumerable<int> {
private int start, end, step;
public Range(int start, int end, int step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
public IEnumerator<int> GetEnumerator() {
for (int i = start; i < end; i += step)
yield return i;
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
// Custom Fibonacci generator
class Fibonacci : IEnumerable<long> {
private int count;
public Fibonacci(int count) {
this.count = count;
}
public IEnumerator<long> GetEnumerator() {
long a = 0, b = 1;
for (int i = 0; i < count; i++) {
yield return a;
long c = a + b;
a = b;
b = c;
}
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
class Program {
static void Main() {
// Custom range
foreach (int x in new Range(1, 11))
Console.Write($"{x} ");
Console.WriteLine();
foreach (int x in new Range(0, 20, 2))
Console.Write($"{x} ");
Console.WriteLine();
// Custom fibonacci
foreach (long x in new Fibonacci(10))
Console.Write($"{x} ");
Console.WriteLine();
// LINQ with custom iterator
var evens = new Range(1, 21).Where(x => x % 2 == 0);
Console.WriteLine($"Evens: {string.Join(" ", evens)}");
// Yield return examples
Console.Write("Manual squares: ");
foreach (int x in Squares(1, 5))
Console.Write($"{x} ");
Console.WriteLine();
}
static IEnumerable<int> Squares(int start, int end) {
for (int i = start; i <= end; i++)
yield return i * i;
}
}Custom Stack uses a List<T> with Add() and RemoveAt(). Custom Queue uses LinkedList<T> for O(1) Enqueue/Dequeue.
- Stack:
List<T>withPush()andPop() - Queue:
LinkedList<T>withAddLast()andRemoveFirst() - Throw
InvalidOperationExceptionon empty - Use
Countproperty for size
// Stack and Queue Implementations
using System;
using System.Collections.Generic;
// Custom Stack
class MyStack<T> {
private List<T> items = new List<T>();
public void Push(T item) {
items.Add(item);
}
public T Pop() {
if (IsEmpty()) throw new InvalidOperationException("Stack is empty");
T item = items[items.Count - 1];
items.RemoveAt(items.Count - 1);
return item;
}
public T Peek() {
if (IsEmpty()) throw new InvalidOperationException("Stack is empty");
return items[items.Count - 1];
}
public bool IsEmpty() => items.Count == 0;
public int Count => items.Count;
}
// Custom Queue
class MyQueue<T> {
private LinkedList<T> items = new LinkedList<T>();
public void Enqueue(T item) {
items.AddLast(item);
}
public T Dequeue() {
if (IsEmpty()) throw new InvalidOperationException("Queue is empty");
T item = items.First.Value;
items.RemoveFirst();
return item;
}
public T Peek() {
if (IsEmpty()) throw new InvalidOperationException("Queue is empty");
return items.First.Value;
}
public bool IsEmpty() => items.Count == 0;
public int Count => items.Count;
}
class Program {
static void Main() {
// Custom Stack
MyStack<int> stack = new MyStack<int>();
stack.Push(10);
stack.Push(20);
stack.Push(30);
Console.WriteLine($"Stack top: {stack.Peek()}");
while (!stack.IsEmpty())
Console.Write($"{stack.Pop()} ");
Console.WriteLine();
// Custom Queue
MyQueue<int> queue = new MyQueue<int>();
queue.Enqueue(10);
queue.Enqueue(20);
queue.Enqueue(30);
Console.WriteLine($"Queue front: {queue.Peek()}");
while (!queue.IsEmpty())
Console.Write($"{queue.Dequeue()} ");
Console.WriteLine();
}
}Counting Sort is O(n+k) for non-negative integers. Radix Sort applies counting sort digit by digit, achieving O(d*(n+k)) for any integer range.
- Counting Sort: frequency array then reconstruct
- Radix Sort: stable sort by each digit position
- Both are non-comparison sorts
Array.Fill()for clean initialization
// Counting Sort and Radix Sort in C#
using System;
using System.Collections.Generic;
class Program {
static void CountingSort(List<int> arr) {
if (arr.Count == 0) return;
int maxVal = arr[0];
foreach (int x in arr) if (x > maxVal) maxVal = x;
int[] count = new int[maxVal + 1];
foreach (int x in arr) count[x]++;
int idx = 0;
for (int i = 0; i <= maxVal; i++)
while (count[i]-- > 0) arr[idx++] = i;
}
static void CountSortByDigit(List<int> arr, int exp) {
int n = arr.Count;
int[] output = new int[n];
int[] count = new int[10];
foreach (int x in arr) count[(x / exp) % 10]++;
for (int i = 1; i < 10; i++) count[i] += count[i - 1];
for (int i = n - 1; i >= 0; i--) {
int digit = (arr[i] / exp) % 10;
output[count[digit] - 1] = arr[i];
count[digit]--;
}
for (int i = 0; i < n; i++) arr[i] = output[i];
}
static void RadixSort(List<int> arr) {
int maxVal = arr[0];
foreach (int x in arr) if (x > maxVal) maxVal = x;
for (int exp = 1; maxVal / exp > 0; exp *= 10)
CountSortByDigit(arr, exp);
}
static void Main() {
List<int> v1 = new List<int> { 4, 2, 2, 8, 3, 3, 1, 7, 5 };
CountingSort(v1);
Console.WriteLine($"Counting: {string.Join(" ", v1)}");
List<int> v2 = new List<int> { 170, 45, 75, 90, 802, 24, 2, 66 };
RadixSort(v2);
Console.WriteLine($"Radix: {string.Join(" ", v2)}");
}
}Cycle detection differs for directed and undirected graphs. Directed graphs use DFS with a recursion stack. Undirected graphs use Union-Find.
- Directed: visited + recursion stack
- Undirected: Union-Find — same component = cycle
- Local function for recursive find
- Time O(V+E) for both approaches
// Graph Cycle Detection in C#
using System;
using System.Collections.Generic;
class Program {
// Directed graph - DFS with recursion stack
static bool DFS(int v, List<int>[] adj, bool[] visited, bool[] recStack) {
visited[v] = recStack[v] = true;
foreach (int u in adj[v]) {
if (!visited[u] && DFS(u, adj, visited, recStack))
return true;
else if (recStack[u]) return true;
}
recStack[v] = false;
return false;
}
static bool HasCycleDirected(int V, List<int>[] adj) {
bool[] visited = new bool[V];
bool[] recStack = new bool[V];
for (int i = 0; i < V; i++)
if (!visited[i] && DFS(i, adj, visited, recStack))
return true;
return false;
}
// Undirected graph - Union Find
static bool HasCycleUndirected(int V, List<(int, int)> edges) {
int[] parent = new int[V];
for (int i = 0; i < V; i++) parent[i] = i;
int Find(int x) {
if (parent[x] != x) parent[x] = Find(parent[x]);
return parent[x];
}
foreach (var (u, v) in edges) {
int pu = Find(u), pv = Find(v);
if (pu == pv) return true;
parent[pu] = pv;
}
return false;
}
static void Main() {
int V = 4;
List<int>[] adj = new List<int>[V];
for (int i = 0; i < V; i++) adj[i] = new List<int>();
adj[0].Add(1); adj[1].Add(2);
adj[2].Add(3); adj[3].Add(1);
Console.WriteLine($"Directed cycle: {HasCycleDirected(V, adj)}");
List<(int, int)> edges = new List<(int, int)> { (0, 1), (1, 2), (2, 0) };
Console.WriteLine($"Undirected cycle: {HasCycleUndirected(3, edges)}");
}
}GroupJoin performs left outer joins. SelectMany flattens nested sequences. ToLookup creates a dictionary-like lookup structure. Aggregate performs custom reductions.
GroupJoin— left outer join with groupingSelectMany— flatten nested collectionsToLookup— key-based grouping (like Dictionary)Aggregate— custom fold with seed
// Advanced LINQ - GroupJoin, SelectMany, etc.
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
class Customer {
public int Id { get; set; }
public string Name { get; set; }
}
class Order {
public int CustomerId { get; set; }
public string Product { get; set; }
public int Quantity { get; set; }
}
static void Main() {
var customers = new List<Customer> {
new Customer { Id = 1, Name = "Alice" },
new Customer { Id = 2, Name = "Bob" },
new Customer { Id = 3, Name = "Carol" }
};
var orders = new List<Order> {
new Order { CustomerId = 1, Product = "Laptop", Quantity = 1 },
new Order { CustomerId = 1, Product = "Mouse", Quantity = 2 },
new Order { CustomerId = 2, Product = "Keyboard", Quantity = 1 },
new Order { CustomerId = 2, Product = "Monitor", Quantity = 3 },
new Order { CustomerId = 2, Product = "Mouse", Quantity = 1 }
};
// GroupJoin (left outer join)
var customerOrders = customers.GroupJoin(
orders,
c => c.Id,
o => o.CustomerId,
(c, o) => new {
c.Name,
Orders = o.Select(x => $"{x.Product} (x{x.Quantity})")
}
);
Console.WriteLine("Customer Orders:");
foreach (var co in customerOrders) {
Console.WriteLine($"{co.Name}: {string.Join(", ", co.Orders)}");
}
// SelectMany (flatten)
var allOrderItems = orders.SelectMany(o =>
Enumerable.Repeat(o.Product, o.Quantity)
).ToList();
Console.WriteLine($"All items: {string.Join(", ", allOrderItems)}");
// ToLookup
var orderLookup = orders.ToLookup(o => o.CustomerId);
foreach (var group in orderLookup) {
Console.WriteLine($"Customer {group.Key} has {group.Count()} orders");
}
// Aggregate with seed
string allProducts = orders
.Select(o => o.Product)
.Aggregate("", (acc, p) => acc == "" ? p : acc + ", " + p);
Console.WriteLine($"All products: {allProducts}");
}
}Reverse Polish Notation (RPN) evaluation and Infix to Postfix conversion are classic stack problems. C# Stack and int.Parse() make implementations clean.
- RPN: push operands, pop two for operators
- Infix to Postfix: shunting-yard algorithm
- Precedence function for operator ordering
- Used in calculators, compilers, and interpreters
// Expression Evaluation using Stack
using System;
using System.Collections.Generic;
class Program {
// Evaluate Reverse Polish Notation
static int EvalRPN(string[] tokens) {
Stack<int> st = new Stack<int>();
foreach (string t in tokens) {
if (t == "+" || t == "-" || t == "*" || t == "/") {
int b = st.Pop();
int a = st.Pop();
st.Push(t == "+" ? a + b :
t == "-" ? a - b :
t == "*" ? a * b :
a / b);
} else {
st.Push(int.Parse(t));
}
}
return st.Pop();
}
// Infix to Postfix
static string InfixToPostfix(string expr) {
Stack<char> ops = new Stack<char>();
List<string> result = new List<string>();
int Precedence(char c) {
return c == '+' || c == '-' ? 1 :
c == '*' || c == '/' ? 2 : 0;
}
foreach (char c in expr) {
if (char.IsDigit(c)) {
result.Add(c.ToString());
} else if (c == '(') {
ops.Push(c);
} else if (c == ')') {
while (ops.Peek() != '(') result.Add(ops.Pop().ToString());
ops.Pop();
} else {
while (ops.Count > 0 && Precedence(ops.Peek()) >= Precedence(c))
result.Add(ops.Pop().ToString());
ops.Push(c);
}
}
while (ops.Count > 0) result.Add(ops.Pop().ToString());
return string.Join(" ", result);
}
static void Main() {
string[] rpn = { "2", "1", "+", "3", "*" };
Console.WriteLine($"RPN eval: {EvalRPN(rpn)}");
Console.WriteLine($"Infix to Postfix: {InfixToPostfix("(2+3)*4")}");
}
}The Strategy pattern selects an algorithm at runtime. The Template Method defines a skeleton algorithm in a base class with abstract steps.
- Strategy: interface with
Sort()method - Context class with
SetStrategy() - Template Method: abstract class with
Process() - Concrete implementations override abstract steps
// Design Patterns - Strategy and Template
using System;
using System.Collections.Generic;
// Strategy Pattern
interface ISortStrategy {
void Sort(List<int> data);
string Name { get; }
}
class BubbleSortStrategy : ISortStrategy {
public string Name => "Bubble Sort";
public void Sort(List<int> data) {
int n = data.Count;
for (int i = 0; i < n - 1; i++) {
bool swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (data[j] > data[j + 1]) {
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break;
}
}
}
class BuiltInSortStrategy : ISortStrategy {
public string Name => "Built-in Sort";
public void Sort(List<int> data) {
data.Sort();
}
}
class SortContext {
private ISortStrategy strategy;
public SortContext(ISortStrategy strategy) {
this.strategy = strategy;
}
public void SetStrategy(ISortStrategy strategy) {
this.strategy = strategy;
}
public void Sort(List<int> data) {
Console.WriteLine($"Using: {strategy.Name}");
strategy.Sort(data);
}
}
// Template Method Pattern
abstract class DataProcessor {
public void Process() {
LoadData();
ProcessData();
SaveResult();
}
protected abstract void LoadData();
protected abstract void ProcessData();
protected abstract void SaveResult();
}
class CSVProcessor : DataProcessor {
protected override void LoadData() {
Console.WriteLine("Loading CSV data...");
}
protected override void ProcessData() {
Console.WriteLine("Processing CSV data...");
}
protected override void SaveResult() {
Console.WriteLine("Saving CSV result...");
}
}
class Program {
static void Main() {
List<int> data = new List<int> { 5, 3, 8, 1, 9, 2 };
SortContext context = new SortContext(new BubbleSortStrategy());
context.Sort(new List<int>(data));
Console.WriteLine(string.Join(" ", data));
context.SetStrategy(new BuiltInSortStrategy());
context.Sort(new List<int>(data));
Console.WriteLine(string.Join(" ", data));
// Template Method
Console.WriteLine("
Template Method:");
DataProcessor processor = new CSVProcessor();
processor.Process();
}
}Rabin-Karp uses polynomial rolling hash to find pattern matches in O(n+m) average time. C# long handles the hash arithmetic with modulo.
- Compute pattern hash and initial window hash
- Roll the hash: remove left, add right
- Verify match with
Substring() - Average O(n+m), worst O(n*m)
// String Matching - Rabin-Karp in C#
using System;
using System.Collections.Generic;
class Program {
static List<int> RabinKarp(string text, string pattern) {
List<int> positions = new List<int>();
int n = text.Length, m = pattern.Length;
const int BASE = 31;
const int MOD = 1000000009;
// Compute hash of pattern and first window
long patHash = 0, winHash = 0, power = 1;
for (int i = 0; i < m; i++) {
patHash = (patHash + (pattern[i] - 'a' + 1) * power) % MOD;
winHash = (winHash + (text[i] - 'a' + 1) * power) % MOD;
if (i < m - 1) power = power * BASE % MOD;
}
for (int i = 0; i <= n - m; i++) {
if (patHash == winHash) {
if (text.Substring(i, m) == pattern)
positions.Add(i);
}
if (i < n - m) {
winHash = (winHash - (text[i] - 'a' + 1) + MOD) % MOD;
winHash = winHash * (MOD + 1 - BASE) % MOD;
winHash = (winHash + (text[i + m] - 'a' + 1) * power) % MOD;
}
}
return positions;
}
static void Main() {
string text = "aabaacaadaabaaba";
string pattern = "aaba";
var pos = RabinKarp(text, pattern);
Console.WriteLine($"Rabin-Karp found at: {string.Join(" ", pos)}");
}
}dynamic enables runtime type checking and late binding. ExpandoObject provides dynamic objects with properties and methods that can be added at runtime.
dynamic— runtime type resolutionExpandoObject— dynamic property and methodIDictionary<string, object>— dynamic property access- Used for dynamic programming and COM interop
// Type Erasure with dynamic in C#
using System;
using System.Collections.Generic;
using System.Dynamic;
class Program {
static void Main() {
// dynamic type
dynamic value = 42;
Console.WriteLine($"Int: {value}");
value = "Hello, World!";
Console.WriteLine($"String: {value}");
value = 3.14159;
Console.WriteLine($"Double: {value}");
// ExpandoObject - dynamic object
dynamic person = new ExpandoObject();
person.Name = "Alice";
person.Age = 25;
person.Greet = new Action(() => {
Console.WriteLine($"Hello, I'm {person.Name}");
});
person.Greet();
// Dictionary to dynamic
var dict = new Dictionary<string, object> {
["Name"] = "Bob",
["Age"] = 30
};
dynamic dynDict = new ExpandoObject();
foreach (var kvp in dict) {
((IDictionary<string, object>)dynDict)[kvp.Key] = kvp.Value;
}
Console.WriteLine($"{dynDict.Name} is {dynDict.Age} years old");
// Type checking
Console.WriteLine($"value is int: {value is int}");
Console.WriteLine($"value is double: {value is double}");
// Generic with type parameter
PrintType(42);
PrintType("hello");
PrintType(3.14);
}
static void PrintType<T>(T val) {
Console.WriteLine($"Type: {typeof(T).Name}, Value: {val}");
}
}C# mixins can be implemented using default interface methods (C# 8+) or extension methods. Default interface methods provide implementation in interfaces.
- Default interface methods (C# 8+)
- Extension methods for static mixins
- Interface inheritance for capability composition
- Used for Printable, Comparable, Serializable
// Advanced OOP - Mixins and Interfaces
using System;
// Interface for printable
interface IPrintable {
void Print();
}
// Interface for comparable
interface IComparable<T> {
int CompareTo(T other);
}
// Mixin using default interface methods (C# 8+)
interface IPrintableMixin : IPrintable {
void PrintWithHeader() {
Console.WriteLine("=== Print Start ===");
Print();
Console.WriteLine("=== Print End ===");
}
}
// Implementation
class Point : IPrintableMixin, IComparable<Point> {
private double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public void Print() {
Console.WriteLine($"Point({x}, {y})");
}
public int CompareTo(Point other) {
double d1 = x * x + y * y;
double d2 = other.x * other.x + other.y * other.y;
return d1.CompareTo(d2);
}
public double Distance => x * x + y * y;
}
class Program {
static void Main() {
Point p1 = new Point(3, 4);
Point p2 = new Point(1, 1);
Point p3 = new Point(3, 4);
p1.Print();
p1.PrintWithHeader();
Console.WriteLine($"p1 == p3: {p1.CompareTo(p3) == 0}");
Console.WriteLine($"p1 > p2: {p1.CompareTo(p2) > 0}");
Console.WriteLine($"p2 < p1: {p2.CompareTo(p1) < 0}");
// With LINQ
var points = new[] { p1, p2, p3 };
var sorted = points.OrderBy(p => p.Distance);
Console.WriteLine("Sorted by distance:");
foreach (var p in sorted) p.Print();
}
}BlockingCollection implements a thread-safe producer-consumer pattern. Add() and GetConsumingEnumerable() provide bounded buffer functionality.
BlockingCollection<T>— thread-safe collectionAdd()— producer adds itemsGetConsumingEnumerable()— consumer iteratesCompleteAdding()— signal completion
// Concurrency - Producer-Consumer with BlockingCollection
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading;
class Program {
static async Task Main() {
var buffer = new BlockingCollection<int>(3);
// Producer
Task producer = Task.Run(() => {
for (int i = 1; i <= 6; i++) {
buffer.Add(i);
Console.WriteLine($"Produced: {i} | Buffer size: {buffer.Count}");
Thread.Sleep(100);
}
buffer.CompleteAdding();
});
// Consumer
Task consumer = Task.Run(() => {
foreach (int item in buffer.GetConsumingEnumerable()) {
Console.WriteLine($"Consumed: {item} | Buffer size: {buffer.Count}");
Thread.Sleep(150);
}
});
await Task.WhenAll(producer, consumer);
// ConcurrentBag example
var bag = new ConcurrentBag<int>();
Parallel.For(0, 10, i => bag.Add(i));
Console.WriteLine($"ConcurrentBag count: {bag.Count}");
// ConcurrentDictionary
var dict = new ConcurrentDictionary<string, int>();
dict.TryAdd("one", 1);
dict.TryAdd("two", 2);
dict.AddOrUpdate("three", 3, (key, old) => old + 1);
dict.AddOrUpdate("one", 0, (key, old) => old + 10);
Console.WriteLine($"ConcurrentDictionary: one={dict["one"]}, three={dict["three"]}");
}
}Records (C# 9) provide immutable data types with value equality. Pattern matching has been enhanced with property patterns, tuple patterns, and list patterns.
record— immutable data typewithexpression — create modified copy- Property patterns — match on object properties
- Tuple patterns — match on tuple elements
// C# 9+ Features - Records and Pattern Matching
using System;
// Records (C# 9)
public record Person(string Name, int Age);
// Record with methods
public record Student(string Name, int Age, string Major) : Person(Name, Age) {
public void Display() => Console.WriteLine($"{Name} ({Age}) studies {Major}");
}
// Positional record with deconstruction
public record Point(double X, double Y) {
public double Distance => Math.Sqrt(X * X + Y * Y);
}
class Program {
static void Main() {
// Record instantiation
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
Person p3 = p1 with { Age = 26 }; // With expression
Console.WriteLine($"p1 == p2: {p1 == p2}");
Console.WriteLine($"p1: {p1}");
Console.WriteLine($"p3: {p3}");
// Pattern matching
object obj = 42;
if (obj is int i && i > 10) {
Console.WriteLine($"Integer: {i}");
}
// Switch expression with patterns
string result = obj switch {
int n when n > 50 => "Large int",
int n when n > 10 => "Medium int",
int n => "Small int",
string s => $"String: {s}",
_ => "Unknown"
};
Console.WriteLine(result);
// Property pattern
if (p1 is Person { Age: 25, Name: "Alice" }) {
Console.WriteLine("Matched Alice, age 25");
}
// Tuple pattern
var (x, y) = (10, 20);
string tupleResult = (x, y) switch {
(0, 0) => "Origin",
(var a, var b) when a == b => "Equal",
_ => "Other"
};
Console.WriteLine($"Tuple result: {tupleResult}");
}
}Span<T> provides stack-allocated memory slices. Memory<T> can be on the heap. ArrayPool reuses arrays. These features enable high-performance zero-allocation code.
Span<T>— stack-only, no allocationMemory<T>— can be on heapArrayPool<T>— rent and return arraysMemoryMarshal— reinterpret bytes
// Advanced C# - Span, Memory, and Performance
using System;
using System.Buffers;
using System.Runtime.InteropServices;
class Program {
static void Main() {
// Span<T> - stack-only, no allocation
Span<int> numbers = stackalloc int[5] { 1, 2, 3, 4, 5 };
Console.WriteLine($"Span: {string.Join(", ", numbers.ToArray())}");
// Slice
var slice = numbers.Slice(1, 3);
Console.WriteLine($"Slice: {string.Join(", ", slice.ToArray())}");
// Modify slice affects original
slice[0] = 99;
Console.WriteLine($"After slice modification: {string.Join(", ", numbers.ToArray())}");
// Memory<T> - can be on heap
Memory<int> memory = new int[] { 10, 20, 30, 40, 50 };
var memorySlice = memory.Slice(1, 3);
Console.WriteLine($"Memory slice: {string.Join(", ", memorySlice.ToArray())}");
// ArrayPool - rent and return
int[] pooled = ArrayPool<int>.Shared.Rent(10);
try {
for (int i = 0; i < 10; i++) pooled[i] = i * 2;
Console.WriteLine($"Pooled: {string.Join(", ", pooled[..10])}");
} finally {
ArrayPool<int>.Shared.Return(pooled);
}
// Unsafe and MemoryMarshal
byte[] bytes = { 1, 2, 3, 4 };
int intValue = MemoryMarshal.Read<int>(bytes);
Console.WriteLine($"Bytes as int: {intValue}");
// String creation with Span
string text = string.Create(10, 42, (span, state) => {
span.Fill('*');
span[0] = 'X';
});
Console.WriteLine($"Created: {text}");
// ReadOnlySpan
ReadOnlySpan<char> readOnly = "Hello, World!".AsSpan();
Console.WriteLine($"ReadOnlySpan: {readOnly[..5].ToString()}");
}
}Reflection enables inspecting and invoking types at runtime. Type.GetMethod() and MethodInfo.Invoke() allow dynamic method calls.
Type.GetMethod()— get method infoMethodInfo.Invoke()— dynamic invocation- Access private fields with
BindingFlags Delegate.CreateDelegate()— create typed delegate
// Reflection and Dynamic Invocation
using System;
using System.Reflection;
class Calculator {
public int Add(int a, int b) => a + b;
public int Multiply(int a, int b) => a * b;
private string _secret = "Hidden";
private string GetSecret() => _secret;
public void Print(string message) => Console.WriteLine(message);
}
class Program {
static void Main() {
Type calcType = typeof(Calculator);
// Create instance
object calc = Activator.CreateInstance(calcType);
// Get and invoke method
MethodInfo addMethod = calcType.GetMethod("Add");
int result = (int)addMethod.Invoke(calc, new object[] { 5, 3 });
Console.WriteLine($"Add(5,3) = {result}");
// Get all methods
Console.WriteLine("
All methods:");
foreach (var m in calcType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
Console.WriteLine($" {m.Name} ({(m.IsPublic ? "public" : "private")})");
}
// Access private field
FieldInfo secretField = calcType.GetField("_secret", BindingFlags.NonPublic | BindingFlags.Instance);
string secret = (string)secretField.GetValue(calc);
Console.WriteLine($"Private field: {secret}");
// Invoke private method
MethodInfo getSecretMethod = calcType.GetMethod("GetSecret", BindingFlags.NonPublic | BindingFlags.Instance);
string secretValue = (string)getSecretMethod.Invoke(calc, null);
Console.WriteLine($"Private method: {secretValue}");
// Dynamic invocation with delegate
Func<int, int, int> addDelegate = (Func<int, int, int>)Delegate.CreateDelegate(
typeof(Func<int, int, int>), calc, addMethod);
Console.WriteLine($"Delegate: {addDelegate(10, 20)}");
}
}Expression Trees represent code as data, enabling dynamic query construction at runtime. Expression.Lambda() builds predicates dynamically.
Expression<T>— code as dataExpression.Property()— property accessExpression.Lambda()— create lambda- Used in dynamic sorting, filtering, projection
// Advanced LINQ - Dynamic Queries and Expression Trees
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
class Program {
static void Main() {
var data = new List<Person> {
new Person { Name = "Alice", Age = 25, City = "NYC" },
new Person { Name = "Bob", Age = 30, City = "LA" },
new Person { Name = "Carol", Age = 22, City = "NYC" },
new Person { Name = "Dave", Age = 35, City = "Chicago" }
};
// Dynamic query building with Expression Trees
Expression<Func<Person, bool>> filter = p => p.Age > 25 && p.City == "NYC";
var result = data.AsQueryable().Where(filter).ToList();
Console.WriteLine($"Filter result: {string.Join(", ", result.Select(p => p.Name))}");
// Dynamic ordering
string sortField = "Age";
var param = Expression.Parameter(typeof(Person), "p");
var property = Expression.Property(param, sortField);
var lambda = Expression.Lambda<Func<Person, object>>(Expression.Convert(property, typeof(object)), param);
var sorted = data.AsQueryable().OrderBy(lambda).ToList();
Console.WriteLine($"Sorted by Age: {string.Join(", ", sorted.Select(p => $"{p.Name}({p.Age})"))}");
// Dynamic select
string[] fields = { "Name", "City" };
var projections = fields.Select(f => {
var prop = Expression.Property(param, f);
return Expression.Lambda<Func<Person, object>>(Expression.Convert(prop, typeof(object)), param);
}).ToArray();
Console.WriteLine("Projections:");
foreach (var p in data) {
var values = projections.Select(proj => proj.Compile()(p));
Console.WriteLine($" {string.Join(", ", values)}");
}
// Group by dynamic
var grouped = data.GroupBy(p => p.City);
foreach (var g in grouped) {
Console.WriteLine($"City: {g.Key} ({g.Count()})");
}
}
class Person {
public string Name { get; set; }
public int Age { get; set; }
public string City { get; set; }
}
}A Bank Account System demonstrates real-world C# OOP: encapsulated classes, exception handling, transaction history, inter-account transfers, and formatted output.
- Transaction history as a list of nested Transaction objects
- Exception safety: throws on invalid amounts or insufficient funds
Console.WriteLine()with formatting for clean output- Static field for auto-incrementing account numbers
// Complete Bank Account System in C#
using System;
using System.Collections.Generic;
using System.Linq;
class BankAccount {
private static int _nextId = 1000;
private string _accountId;
private string _owner;
private decimal _balance;
private List<Transaction> _transactions = new List<Transaction>();
public BankAccount(string owner, decimal initialDeposit = 0) {
_accountId = "ACC" + (++_nextId);
_owner = owner;
_balance = 0;
if (initialDeposit > 0) Deposit(initialDeposit, "Initial deposit");
}
public void Deposit(decimal amount, string description = "Deposit") {
if (amount <= 0) throw new ArgumentException("Deposit amount must be positive");
_balance += amount;
_transactions.Add(new Transaction("DEPOSIT", amount, description, _balance));
}
public void Withdraw(decimal amount, string description = "Withdrawal") {
if (amount <= 0) throw new ArgumentException("Withdrawal amount must be positive");
if (amount > _balance) throw new InvalidOperationException("Insufficient funds");
_balance -= amount;
_transactions.Add(new Transaction("WITHDRAWAL", amount, description, _balance));
}
public void Transfer(BankAccount target, decimal amount) {
Withdraw(amount, "Transfer to " + target._accountId);
target.Deposit(amount, "Transfer from " + _accountId);
}
public void PrintStatement() {
Console.WriteLine(new string('=', 60));
Console.WriteLine("Account: " + _accountId + " | Owner: " + _owner + " | Balance: $" + _balance.ToString("F2"));
Console.WriteLine(new string('-', 60));
foreach (var t in _transactions) t.Print();
Console.WriteLine(new string('=', 60));
}
public decimal Balance => _balance;
public string AccountId => _accountId;
public string Owner => _owner;
class Transaction {
public string Type { get; }
public decimal Amount { get; }
public string Description { get; }
public decimal BalanceAfter { get; }
public DateTime Timestamp { get; }
public Transaction(string type, decimal amount, string description, decimal balanceAfter) {
Type = type;
Amount = amount;
Description = description;
BalanceAfter = balanceAfter;
Timestamp = DateTime.Now;
}
public void Print() {
Console.WriteLine(Timestamp.ToString("HH:mm:ss") + " | " + Type.PadRight(10) + " | $" + Amount.ToString("F2").PadLeft(9) + " | $" + BalanceAfter.ToString("F2").PadLeft(9) + " | " + Description);
}
}
}
class Bank {
private string _name;
private List<BankAccount> _accounts = new List<BankAccount>();
public Bank(string name) => _name = name;
public BankAccount CreateAccount(string owner, decimal initial = 0) {
var acc = new BankAccount(owner, initial);
_accounts.Add(acc);
Console.WriteLine("Account created: " + acc.AccountId + " for " + owner);
return acc;
}
public void ListAccounts() {
Console.WriteLine("\n=== " + _name + " - All Accounts ===");
foreach (var acc in _accounts)
Console.WriteLine(acc.AccountId + " | " + acc.Owner.PadRight(15) + " | Balance: $" + acc.Balance.ToString("F2"));
}
public decimal TotalAssets => _accounts.Sum(a => a.Balance);
}
class Program {
static void Main() {
Bank bank = new Bank("C# National Bank");
var alice = bank.CreateAccount("Alice Johnson", 5000);
var bob = bank.CreateAccount("Bob Smith", 3000);
var carol = bank.CreateAccount("Carol White", 1000);
alice.Deposit(2000, "Salary");
alice.Withdraw(500, "Rent");
alice.Transfer(bob, 1000);
try {
carol.Withdraw(5000);
} catch (InvalidOperationException e) {
Console.WriteLine("Error: " + e.Message);
}
bob.Deposit(200, "Freelance payment");
carol.Deposit(3000, "Bonus");
carol.Transfer(alice, 500);
alice.PrintStatement();
bob.PrintStatement();
carol.PrintStatement();
bank.ListAccounts();
Console.WriteLine("Total Assets: $" + bank.TotalAssets.ToString("F2"));
}
}IDDFS combines DFS's space efficiency with BFS's completeness. It repeatedly runs depth-limited DFS with increasing depth limits until the target is found.
- Combines O(bd) space with BFS optimality
- Backtrack visited array after each DLS call
- Finds shortest path in unweighted graphs
- Used in puzzle solving and game tree search
// Iterative Deepening DFS (IDDFS) in C#
using System;
using System.Collections.Generic;
class Program {
static bool DLS(List<int>[] adj, int curr, int target,
int depth, bool[] visited) {
if (curr == target) return true;
if (depth == 0) return false;
visited[curr] = true;
foreach (int next in adj[curr]) {
if (!visited[next])
if (DLS(adj, next, target, depth - 1, visited))
return true;
}
visited[curr] = false;
return false;
}
static bool IDDFS(List<int>[] adj, int src, int target, int maxDepth) {
for (int depth = 0; depth <= maxDepth; depth++) {
bool[] visited = new bool[adj.Length];
Console.WriteLine("Searching at depth " + depth + "...");
if (DLS(adj, src, target, depth, visited))
return true;
}
return false;
}
static void Main() {
int V = 7;
List<int>[] adj = new List<int>[V];
for (int i = 0; i < V; i++) adj[i] = new List<int>();
adj[0] = new List<int> { 1, 2 };
adj[1] = new List<int> { 3, 4 };
adj[2] = new List<int> { 5, 6 };
Console.WriteLine("IDDFS: Search for node 6 from 0");
bool found = IDDFS(adj, 0, 6, 5);
Console.WriteLine("Found: " + (found ? "Yes" : "No"));
Console.WriteLine("\nIDDFS: Search for node 9 from 0 (not exists)");
found = IDDFS(adj, 0, 9, 3);
Console.WriteLine("Found: " + (found ? "Yes" : "No"));
}
}A Sparse Table preprocesses an array in O(n log n) to answer Range Minimum Queries in O(1) time. It exploits overlapping ranges of powers of 2.
- Build: precompute minimums for all power-of-2 lengths
- Query: use two overlapping ranges that cover [l, r]
- Query Time O(1) — fastest possible
- Cannot handle updates (static structure)
// Sparse Table for Range Minimum Query
using System;
using System.Collections.Generic;
class SparseTable {
private int[][] table;
private int[] log2;
public SparseTable(int[] arr) {
int n = arr.Length;
int LOG = (int)Math.Floor(Math.Log2(n)) + 1;
table = new int[LOG][];
log2 = new int[n + 1];
// Precompute log2
log2[1] = 0;
for (int i = 2; i <= n; i++)
log2[i] = log2[i / 2] + 1;
// Build sparse table
table[0] = arr;
for (int j = 1; j < LOG; j++) {
table[j] = new int[n - (1 << j) + 1];
for (int i = 0; i + (1 << j) <= n; i++)
table[j][i] = Math.Min(table[j - 1][i],
table[j - 1][i + (1 << (j - 1))]);
}
}
public int Query(int l, int r) {
int k = log2[r - l + 1];
return Math.Min(table[k][l], table[k][r - (1 << k) + 1]);
}
}
class Program {
static void Main() {
int[] arr = { 2, 4, 3, 1, 6, 7, 8, 9, 1, 7 };
SparseTable st = new SparseTable(arr);
Console.WriteLine("RMQ(0,4): " + st.Query(0, 4));
Console.WriteLine("RMQ(2,7): " + st.Query(2, 7));
Console.WriteLine("RMQ(5,9): " + st.Query(5, 9));
Console.WriteLine("RMQ(0,2): " + st.Query(0, 2));
}
}A Fenwick Tree (Binary Indexed Tree) supports prefix sum queries and point updates in O(log n). The lowbit operation i & -i is key for traversal.
- Update: add to i, then
i += i & -i - Query: sum from i, then
i -= i & -i - Range query:
Query(r) - Query(l-1) - Simpler and faster than Segment Tree for sum queries
// Fenwick Tree (Binary Indexed Tree) in C#
using System;
class FenwickTree {
private int[] tree;
private int n;
public FenwickTree(int n) {
this.n = n;
tree = new int[n + 1];
}
public void Update(int i, int delta) {
for (; i <= n; i += i & -i)
tree[i] += delta;
}
public int Query(int i) {
int sum = 0;
for (; i > 0; i -= i & -i)
sum += tree[i];
return sum;
}
public int RangeQuery(int l, int r) => Query(r) - Query(l - 1);
public void Build(int[] arr) {
for (int i = 0; i < arr.Length; i++)
Update(i + 1, arr[i]);
}
}
class Program {
static void Main() {
int[] arr = { 1, 3, 5, 7, 9, 11 };
FenwickTree ft = new FenwickTree(arr.Length);
ft.Build(arr);
Console.WriteLine("Prefix sum [1,3]: " + ft.RangeQuery(1, 3));
Console.WriteLine("Prefix sum [2,5]: " + ft.RangeQuery(2, 5));
Console.WriteLine("Total sum: " + ft.RangeQuery(1, 6));
ft.Update(3, 6);
Console.WriteLine("After update(3,6):");
Console.WriteLine("Prefix sum [1,3]: " + ft.RangeQuery(1, 3));
Console.WriteLine("Total sum: " + ft.RangeQuery(1, 6));
}
}Shell Sort generalizes Insertion Sort using decreasing gap sequences. Interpolation Search estimates position proportionally, achieving O(log log n) on uniform distributions.
- Shell Sort gap starts at n/2, halves each pass
- In-place, not stable, better than Insertion Sort
- Interpolation Search: best for uniform sorted arrays
- Degrades to O(n) worst case
// Shell Sort and Interpolation Search in C#
using System;
using System.Collections.Generic;
class Program {
static void ShellSort(List<int> arr) {
int n = arr.Count;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = arr[i], j = i;
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
}
static int InterpolationSearch(int[] arr, int target) {
int low = 0, high = arr.Length - 1;
while (low <= high &&
target >= arr[low] &&
target <= arr[high]) {
if (low == high) {
return arr[low] == target ? low : -1;
}
int pos = low + (int)((double)(high - low) /
(arr[high] - arr[low]) * (target - arr[low]));
if (arr[pos] == target) return pos;
if (arr[pos] < target) low = pos + 1;
else high = pos - 1;
}
return -1;
}
static void Main() {
List<int> arr = new List<int> { 64, 34, 25, 12, 22, 11, 90, 1, 55, 47 };
Console.WriteLine("Before: " + string.Join(" ", arr));
ShellSort(arr);
Console.WriteLine("After Shell Sort: " + string.Join(" ", arr));
int[] sorted = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
Console.WriteLine("Search 70: index = " + InterpolationSearch(sorted, 70));
Console.WriteLine("Search 45: index = " + InterpolationSearch(sorted, 45));
Console.WriteLine("Search 100: index = " + InterpolationSearch(sorted, 100));
}
}Nullable reference types (C# 8+) help prevent null reference exceptions. Pattern matching with property patterns, tuple patterns, and list patterns (C# 11) enables concise, readable code.
#nullable enable— enable nullable checksstring?— nullable reference type- Property patterns:
{ Name: "Alice", Age: 25 } - List patterns:
[1, 2, 3]
// Advanced C# - Nullable Reference Types and Pattern Matching
using System;
#nullable enable
class Person {
public string Name { get; set; } = "";
public int Age { get; set; }
public string? Email { get; set; }
}
class Program {
static void Main() {
// Nullable reference types
string? maybeNull = null;
string notNull = "Hello";
// Null check
if (maybeNull != null) {
Console.WriteLine(maybeNull.Length);
}
// Null coalescing
string value = maybeNull ?? "Default";
// Null conditional
int? length = maybeNull?.Length;
// Pattern matching with null
string result = maybeNull switch {
null => "Null value",
string s when s.Length > 10 => "Long string",
string s => "Length: " + s.Length,
_ => "Unknown"
};
Console.WriteLine(result);
// Property pattern matching
Person person = new Person { Name = "Alice", Age = 25, Email = "alice@email.com" };
string matchResult = person switch {
{ Name: "Alice", Age: 25 } => "Alice, age 25",
{ Email: not null } => "Has email: " + person.Email,
_ => "Other"
};
Console.WriteLine(matchResult);
// Tuple pattern
var tuple = (Name: "Bob", Age: 30);
string tupleResult = tuple switch {
("Bob", 30) => "Bob, 30",
(_, > 18) => "Adult: " + tuple.Name,
_ => "Minor"
};
Console.WriteLine(tupleResult);
// List pattern (C# 11)
int[] numbers = { 1, 2, 3 };
string listResult = numbers switch {
[1, 2, 3] => "Exactly [1,2,3]",
[1, _, 3] => "Starts with 1, ends with 3",
[_, _, _] => "Three elements",
_ => "Other"
};
Console.WriteLine(listResult);
}
}Task and Task<T> enable asynchronous programming. PLINQ (AsParallel()) provides parallel LINQ queries. Parallel.For and Parallel.ForEach handle parallel loops.
Task.Run()— run on thread poolTask.WhenAll()— wait for multipleAsParallel()— parallel LINQ queriesCancellationToken— graceful cancellation
// Multithreading with Tasks and Parallel LINQ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program {
static async Task Main() {
// Parallel sum using tasks
int[] arr = Enumerable.Repeat(1, 1000000).ToArray();
long total = await ParallelSum(arr, 0, arr.Length);
Console.WriteLine("Parallel sum: " + total);
// Multiple async tasks
var tasks = new List<Task<string>>();
for (int i = 1; i <= 3; i++) {
int id = i;
tasks.Add(FetchDataAsync(id));
}
var results = await Task.WhenAll(tasks);
Console.WriteLine("Results: " + string.Join(", ", results));
// PLINQ
var numbers = Enumerable.Range(1, 100);
var squares = numbers.AsParallel()
.Select(n => n * n)
.ToList();
Console.WriteLine("First 5 squares: " + string.Join(", ", squares.Take(5)));
// Parallel.For with cancellation
var cts = new CancellationTokenSource();
cts.CancelAfter(500);
try {
Parallel.For(0, 10, new ParallelOptions { CancellationToken = cts.Token }, i => {
Thread.Sleep(100);
Console.WriteLine("Task " + i + " on thread " + Thread.CurrentThread.ManagedThreadId);
});
} catch (OperationCanceledException) {
Console.WriteLine("Parallel.For cancelled!");
}
// Parallel.ForEach
var items = Enumerable.Range(1, 20);
Parallel.ForEach(items, item => {
Thread.Sleep(50);
Console.WriteLine("Processed " + item);
});
}
static async Task<long> ParallelSum(int[] arr, int l, int r) {
if (r - l <= 100000) {
return arr.Skip(l).Take(r - l).Sum(x => (long)x);
}
int mid = (l + r) / 2;
var left = ParallelSum(arr, l, mid);
var right = ParallelSum(arr, mid, r);
return await left + await right;
}
static async Task<string> FetchDataAsync(int id) {
await Task.Delay(100);
return "Data from source " + id;
}
}A Library Management System demonstrates comprehensive C# OOP: multiple classes, Dictionary for O(1) lookups, exception handling, and formatted output — all working together.
Dictionary<string, Book>for O(1) ISBN lookup- Full CRUD — add, remove, borrow, return, search, display
- String interpolation for clean console output
- LINQ for sorting and searching
// Complete Library Management System in C#
using System;
using System.Collections.Generic;
using System.Linq;
class Book {
public string ISBN { get; }
public string Title { get; }
public string Author { get; }
public string Genre { get; }
public int Year { get; }
public int TotalCopies { get; private set; }
public int AvailableCopies { get; private set; }
public Book(string isbn, string title, string author,
string genre, int year, int copies = 1) {
ISBN = isbn;
Title = title;
Author = author;
Genre = genre;
Year = year;
TotalCopies = copies;
AvailableCopies = copies;
}
public bool IsAvailable => AvailableCopies > 0;
public void Checkout() {
if (IsAvailable) AvailableCopies--;
}
public void Return() {
if (AvailableCopies < TotalCopies) AvailableCopies++;
}
public void Display() {
Console.WriteLine(ISBN.PadRight(15) + " " + Title.PadRight(30) + " " + Author.PadRight(20) + " " + Genre.PadRight(12) + " " + Year.ToString().PadRight(6) + " [" + AvailableCopies + "/" + TotalCopies + "]");
}
}
class Member {
private static int _nextId = 1;
public string Id { get; }
public string Name { get; }
public string Email { get; }
private List<string> _borrowedISBNs = new List<string>();
private const int MAX_BORROW = 5;
public Member(string name, string email) {
Id = "M" + (_nextId++).ToString("D3");
Name = name;
Email = email;
}
public bool CanBorrow => _borrowedISBNs.Count < MAX_BORROW;
public void Borrow(string isbn) => _borrowedISBNs.Add(isbn);
public void Return(string isbn) => _borrowedISBNs.Remove(isbn);
public bool HasBorrowed(string isbn) => _borrowedISBNs.Contains(isbn);
public void Display() {
Console.WriteLine("Member [" + Id + "] " + Name + " | Email: " + Email + " | Borrowed: " + _borrowedISBNs.Count + "/" + MAX_BORROW);
if (_borrowedISBNs.Any())
Console.WriteLine(" Books: " + string.Join(", ", _borrowedISBNs));
}
}
class Library {
private string _name;
private Dictionary<string, Book> _books = new Dictionary<string, Book>();
private Dictionary<string, Member> _members = new Dictionary<string, Member>();
public Library(string name) => _name = name;
public void AddBook(Book book) {
_books[book.ISBN] = book;
Console.WriteLine("Book added: " + book.Title);
}
public void RegisterMember(Member member) {
_members[member.Id] = member;
Console.WriteLine("Member registered: " + member.Name);
}
public void BorrowBook(string memberId, string isbn) {
var member = GetMember(memberId);
var book = GetBook(isbn);
if (!member.CanBorrow)
throw new InvalidOperationException(member.Name + " has reached borrow limit");
if (!book.IsAvailable)
throw new InvalidOperationException("Book not available: " + book.Title);
book.Checkout();
member.Borrow(isbn);
Console.WriteLine(member.Name + " borrowed: " + book.Title);
}
public void ReturnBook(string memberId, string isbn) {
var member = GetMember(memberId);
var book = GetBook(isbn);
if (!member.HasBorrowed(isbn))
throw new InvalidOperationException(member.Name + " did not borrow this book");
book.Return();
member.Return(isbn);
Console.WriteLine(member.Name + " returned: " + book.Title);
}
public List<Book> SearchByAuthor(string author) =>
_books.Values.Where(b => b.Author.Contains(author)).ToList();
public List<Book> SearchByGenre(string genre) =>
_books.Values.Where(b => b.Genre == genre).ToList();
public void DisplayAllBooks() {
Console.WriteLine("\n=== " + _name + " - Catalog ===");
Console.WriteLine("ISBN".PadRight(15) + "Title".PadRight(30) + "Author".PadRight(20) + "Genre".PadRight(12) + "Year".PadRight(6) + "Copies");
Console.WriteLine(new string('-', 90));
foreach (var book in _books.Values.OrderBy(b => b.Title))
book.Display();
}
public void DisplayAllMembers() {
Console.WriteLine("\n=== " + _name + " - Members ===");
foreach (var member in _members.Values)
member.Display();
}
public void DisplayStats() {
int total = _books.Count;
int available = _books.Values.Count(b => b.IsAvailable);
Console.WriteLine("\n=== Stats ===");
Console.WriteLine("Total books: " + total);
Console.WriteLine("Available: " + available);
Console.WriteLine("Checked out: " + (total - available));
Console.WriteLine("Total members: " + _members.Count);
}
private Book GetBook(string isbn) {
if (!_books.TryGetValue(isbn, out var book))
throw new KeyNotFoundException("Book not found: " + isbn);
return book;
}
private Member GetMember(string id) {
if (!_members.TryGetValue(id, out var member))
throw new KeyNotFoundException("Member not found: " + id);
return member;
}
}
class Program {
static void Main() {
Library lib = new Library("C# City Library");
// Add books
lib.AddBook(new Book("978-0", "The C# Book", "Anders Hejlsberg", "Programming", 2020, 3));
lib.AddBook(new Book("978-1", "Design Patterns", "Gang of Four", "Programming", 2015, 2));
lib.AddBook(new Book("978-2", "Clean Code", "Robert Martin", "Programming", 2008, 4));
lib.AddBook(new Book("978-3", "Dune", "Frank Herbert", "Sci-Fi", 1965, 2));
lib.AddBook(new Book("978-4", "1984", "George Orwell", "Fiction", 1949, 3));
// Register members
lib.RegisterMember(new Member("Alice Johnson", "alice@email.com"));
lib.RegisterMember(new Member("Bob Smith", "bob@email.com"));
lib.RegisterMember(new Member("Carol White", "carol@email.com"));
lib.DisplayAllBooks();
// Borrow books
lib.BorrowBook("M001", "978-0");
lib.BorrowBook("M001", "978-2");
lib.BorrowBook("M002", "978-1");
lib.BorrowBook("M003", "978-3");
try {
lib.BorrowBook("M001", "978-9");
} catch (KeyNotFoundException e) {
Console.WriteLine("Error: " + e.Message);
}
lib.ReturnBook("M001", "978-0");
lib.BorrowBook("M002", "978-0");
Console.WriteLine("\nSearch by genre 'Programming':");
foreach (var book in lib.SearchByGenre("Programming"))
book.Display();
lib.DisplayAllMembers();
lib.DisplayStats();
}
}