================================================================================ CSHARP-CHEATSHEET.TXT ================================================================================ NAME csharp-cheatsheet.txt -- offline C# / .NET reference manual SYNOPSIS grep -A 40 "^SECTION_NAME" csharp-cheatsheet.txt less csharp-cheatsheet.txt grep -n "SECTION_NAME" csharp-cheatsheet.txt DESCRIPTION This file is a single flat plain-text reference manual covering the .NET CLI, the C# language, the base class library collections, LINQ, common interview / LeetCode-style algorithms and data structures, async programming, ASP.NET Core, Entity Framework Core, JSON, file I/O, networking, PostgreSQL, debugging, design patterns, and general best practices. It is written to be grepped, not rendered. Every major topic has a large banner header made of '=' characters. Every subsection inside a topic has a smaller banner made of '-' characters. There is no markdown, no bullets unless natural, and no code fences. Code is always indented four spaces. SUGGESTED ALIAS Add something like this to your shell rc file: cscs() { if [ -z "$1" ]; then less ~/csharp-cheatsheet.txt else grep -n -i --color=always "$*" ~/csharp-cheatsheet.txt | less -R fi } Usage: cscs LIST cscs DICTIONARY cscs "TREE DFS" cscs DOTNET CLI cscs LINQ Since every section header is a unique all-caps banner line, grep -n will point you straight at the line number, and you can jump there directly with: less + ~/csharp-cheatsheet.txt TABLE OF CONTENTS NAMESPACES MATH SKELETON CODE DOTNET CLI SOLUTIONS AND PROJECTS NUGET VARIABLES AND TYPES OPERATORS CONTROL FLOW METHODS AND PARAMETERS CLASSES STRUCTS RECORDS ENUMS INTERFACES INHERITANCE AND POLYMORPHISM GENERICS DELEGATES AND EVENTS EXTENSION METHODS EXCEPTION HANDLING IDISPOSABLE AND USING STRINGS ARRAY LIST LINKEDLIST DICTIONARY HASHSET QUEUE STACK PRIORITY QUEUE LINQ BINARY SEARCH TREE DFS TREE BFS BACKTRACKING SLIDING WINDOW TWO POINTERS UNION FIND TRIE HEAP RECURSION AND MEMOIZATION DYNAMIC PROGRAMMING BIT MANIPULATION GRAPHS ASYNC ASPNET CORE ENTITY FRAMEWORK CORE JSON FILE IO NETWORKING POSTGRESQL COMMON EXCEPTIONS DEBUGGING DESIGN PATTERNS BEST PRACTICES LEETCODE ================================================================================ NAMESPACES ================================================================================ using System; Console Math Random Convert DateTime DateOnly TimeOnly TimeSpan Environment Exception Enum using System.Collections.Generic; List Dictionary HashSet Queue Stack PriorityQueue LinkedList SortedDictionary SortedSet using System.Linq; Min() Max() Sum() Average() Count() Where() Select() SelectMany() OrderBy() OrderByDescending() ThenBy() Distinct() GroupBy() ToList() ToArray() ToDictionary() Any() All() First() FirstOrDefault() Last() LastOrDefault() Single() Skip() Take() using System.Text; StringBuilder Encoding using System.Text.Json; JsonSerializer JsonDocument JsonElement JsonSerializerOptions using System.Text.RegularExpressions; Regex Match MatchCollection using System.IO; File FileInfo Directory DirectoryInfo Path StreamReader StreamWriter FileStream using System.Threading; Thread CancellationToken CancellationTokenSource Monitor Mutex Semaphore SemaphoreSlim using System.Threading.Tasks; Task Task ValueTask Parallel Parallel.ForEach() using System.Diagnostics; Debug Stopwatch Process using System.Net.Http; HttpClient HttpRequestMessage HttpResponseMessage using System.Security.Cryptography; SHA256 SHA512 MD5 RandomNumberGenerator using System.Collections.Concurrent; ConcurrentDictionary ConcurrentQueue ConcurrentStack BlockingCollection Notes • Modern .NET (6+) enables Implicit Usings by default. • Most console applications do not require writing these using directives manually. • LINQ methods (Min, Max, Sum, Where, Select, etc.) require: using System.Linq; ================================================================================ MATH ================================================================================ Minimum Math.Min(a, b); Maximum Math.Max(a, b); Absolute Value Math.Abs(x); Power Math.Pow(x, y); Square Root Math.Sqrt(x); Round Math.Round(x); Ceiling Math.Ceiling(x); Floor Math.Floor(x); Truncate Math.Truncate(x); Clamp Math.Clamp(value, min, max); Sign Math.Sign(x); Remainder x % y DivRem Math.DivRem(a, b); Maximum Integer int.MaxValue Minimum Integer int.MinValue Maximum Long long.MaxValue Minimum Long long.MinValue Maximum Double double.MaxValue Minimum Double double.MinValue Positive Infinity double.PositiveInfinity Negative Infinity double.NegativeInfinity NaN double.NaN Check NaN double.IsNaN(x); Check Infinity double.IsInfinity(x); PI Math.PI Euler's Number Math.E Sin Math.Sin(radians); Cos Math.Cos(radians); Tan Math.Tan(radians); Asin Math.Asin(x); Acos Math.Acos(x); Atan Math.Atan(x); Atan2 Math.Atan2(y, x); Log Base e Math.Log(x); Log Base 10 Math.Log10(x); Log Base N Math.Log(x, baseValue); Exponential Math.Exp(x); BigMul Math.BigMul(a, b); Fused Multiply Add (.NET 8+) Math.FusedMultiplyAdd(a, b, c); Greatest Value in Array nums.Max(); Smallest Value in Array nums.Min(); Sum Array nums.Sum(); Average nums.Average(); ================================================================================ RANDOM ================================================================================ Create Random random = new Random(); Random Integer random.Next(); Random Range random.Next(min, max); Random Double random.NextDouble(); ================================================================================ COMMON LEETCODE MATH ================================================================================ Middle Index int mid = left + (right - left) / 2; Avoid Integer Overflow long result = (long)a * b; Swap (a, b) = (b, a); Even x % 2 == 0 Odd x % 2 != 0 Max of Three Math.Max(a, Math.Max(b, c)); Min of Three Math.Min(a, Math.Min(b, c)); ================================================================================ SKELETON CODE ================================================================================ using System; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } } ================================================================================ DOTNET CLI ================================================================================ DESCRIPTION The dotnet command line interface is the entry point for creating, buil Single() Skip() Take() ding, running, testing, and publishing .NET projects. It replaces most of what used to require Visual Studio's GUI and works identically on Linux, macOS, and Windows. ------------------------------------------------------------------------------- DOTNET NEW ------------------------------------------------------------------------------- Scaffolds a new project or file from a template. dotnet new console -o MyApp dotnet new classlib -o MyLib dotnet new webapi -o MyApi dotnet new mvc -o MyMvcApp dotnet new xunit -o MyApp.Tests dotnet new sln -o MySolution dotnet new gitignore dotnet new editorconfig List every installed template: dotnet new list Search for a template on NuGet.org: dotnet new search blazor ------------------------------------------------------------------------------- DOTNET RUN ------------------------------------------------------------------------------- Builds (if needed) and runs the project in the current directory. dotnet run Pass arguments to your program after a double dash: dotnet run -- --port 5000 --verbose Run a specific project when several exist: dotnet run --project ./src/MyApp/MyApp.csproj Run in a specific configuration: dotnet run -c Release ------------------------------------------------------------------------------- DOTNET BUILD ------------------------------------------------------------------------------- Compiles the project and its dependencies without running it. dotnet build dotnet build -c Release dotnet build -c Release -o ./out dotnet build --no-restore dotnet build --arch x64 ------------------------------------------------------------------------------- DOTNET PUBLISH ------------------------------------------------------------------------------- Produces a deployable set of files (framework-dependent or self-contained). dotnet publish -c Release -o ./publish Self-contained single-file publish for Linux x64: dotnet publish -c Release -r linux-x64 --self-contained true \ -p:PublishSingleFile=true -o ./publish Trim unused assemblies (AOT-friendly): dotnet publish -c Release -r linux-x64 --self-contained true \ -p:PublishTrimmed=true ------------------------------------------------------------------------------- DOTNET RESTORE ------------------------------------------------------------------------------- Downloads and installs NuGet dependencies listed in the project file. This runs implicitly before build/run/publish/test, but can be called directly. dotnet restore dotnet restore --force dotnet restore --source https://api.nuget.org/v3/index.json ------------------------------------------------------------------------------- DOTNET CLEAN ------------------------------------------------------------------------------- Removes build output (bin/obj) produced by previous builds. dotnet clean dotnet clean -c Release ------------------------------------------------------------------------------- DOTNET WATCH ------------------------------------------------------------------------------- Watches source files and automatically rebuilds/reruns/retests on change. Extremely useful during API development. dotnet watch run dotnet watch test dotnet watch --project ./src/MyApi/MyApi.csproj run ------------------------------------------------------------------------------- DOTNET TEST ------------------------------------------------------------------------------- Runs unit tests using the test runner configured in the project (xUnit, NUnit, MSTest). dotnet test dotnet test --filter "FullyQualifiedName~MyNamespace.MyTests" dotnet test --logger "console;verbosity=detailed" dotnet test --collect:"XPlat Code Coverage" ------------------------------------------------------------------------------- DOTNET TOOL ------------------------------------------------------------------------------- Manages .NET global and local command-line tools. dotnet tool install -g dotnet-ef dotnet tool install -g dotnet-outdated-tool dotnet tool list -g dotnet tool update -g dotnet-ef dotnet tool uninstall -g dotnet-ef Local (project-scoped) tools use a tool manifest: dotnet new tool-manifest dotnet tool install dotnet-ef dotnet tool restore ------------------------------------------------------------------------------- DOTNET WORKLOAD ------------------------------------------------------------------------------- Manages optional workloads such as mobile (MAUI), WebAssembly, and other SDK extensions. dotnet workload list dotnet workload install maui dotnet workload update dotnet workload search ------------------------------------------------------------------------------- DOTNET SLN ------------------------------------------------------------------------------- Manages a .sln solution file's list of projects. dotnet new sln -n MySolution dotnet sln add src/MyApi/MyApi.csproj dotnet sln add src/**/*.csproj dotnet sln remove src/MyApi/MyApi.csproj dotnet sln list ------------------------------------------------------------------------------- DOTNET ADD ------------------------------------------------------------------------------- Adds a package reference or project reference. dotnet add package Newtonsoft.Json dotnet add package Npgsql --version 8.0.3 dotnet add reference ../MyLib/MyLib.csproj ------------------------------------------------------------------------------- DOTNET REMOVE ------------------------------------------------------------------------------- Removes a package reference or project reference. dotnet remove package Newtonsoft.Json dotnet remove reference ../MyLib/MyLib.csproj ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Running dotnet run from the solution root when multiple projects exist causes an ambiguous "Specify which project" error. Either cd into the project directory or pass --project explicitly. Forgetting dotnet restore after editing a .csproj by hand -- most IDEs do this automatically, but the raw CLI does not always pick up changes without a restore. Confusing dotnet build output with dotnet publish output. Build output is not meant for deployment; publish output is. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know the difference between framework-dependent deployment (FDD) and self-contained deployment (SCD). FDD requires the .NET runtime to be installed on the target machine; SCD bundles the runtime with the app. Know that dotnet is a single entry point binary; the actual SDK tools are resolved based on the global.json file or the latest installed SDK. ================================================================================ SOLUTIONS AND PROJECTS ================================================================================ DESCRIPTION A .sln file groups multiple .csproj projects together (a web API, a class library, a test project, etc). Solutions are optional for a single project but standard for anything real-world. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- mkdir MyProduct && cd MyProduct dotnet new sln -n MyProduct dotnet new webapi -o src/MyProduct.Api dotnet new classlib -o src/MyProduct.Core dotnet new classlib -o src/MyProduct.Infrastructure dotnet new xunit -o tests/MyProduct.Tests ------------------------------------------------------------------------------- ADDING PROJECTS ------------------------------------------------------------------------------- dotnet sln add src/MyProduct.Api/MyProduct.Api.csproj dotnet sln add src/MyProduct.Core/MyProduct.Core.csproj dotnet sln add src/MyProduct.Infrastructure/MyProduct.Infrastructure.csproj dotnet sln add tests/MyProduct.Tests/MyProduct.Tests.csproj Add every csproj under a folder at once (shell glob, bash/zsh): dotnet sln add (find . -name "*.csproj") ------------------------------------------------------------------------------- REMOVING PROJECTS ------------------------------------------------------------------------------- dotnet sln remove src/MyProduct.Infrastructure/MyProduct.Infrastructure.csproj Removing from the solution does not delete files on disk. It only removes the reference from the .sln. ------------------------------------------------------------------------------- LISTING PROJECTS ------------------------------------------------------------------------------- dotnet sln list ------------------------------------------------------------------------------- REFERENCE PROJECTS ------------------------------------------------------------------------------- Project references let one project use types from another. Run this from inside the project that needs the dependency. cd src/MyProduct.Api dotnet add reference ../MyProduct.Core/MyProduct.Core.csproj dotnet add reference ../MyProduct.Infrastructure/MyProduct.Infrastructure.csproj View existing references: dotnet list reference Typical layered dependency direction: Api -> Core, Infrastructure Infrastructure -> Core Core -> (nothing, pure domain logic) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Creating a circular reference (Core referencing Infrastructure while Infrastructure references Core) will fail to build. Keep dependencies flowing one direction, typically toward Core. Forgetting to add a newly created project to the .sln means IDEs like Rider or Visual Studio will not show it, even though dotnet build from that project's own folder still works. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain Clean Architecture / Onion Architecture layering (Domain / Core at the center, Infrastructure and Api on the outside depending inward). This project layout question comes up often in mid-to-senior .NET interviews. ================================================================================ NUGET ================================================================================ DESCRIPTION NuGet is the package manager for .NET, comparable to npm for Node or pip for Python. Packages are distributed as .nupkg files and consumed via package references in the .csproj. ------------------------------------------------------------------------------- INSTALLING ------------------------------------------------------------------------------- dotnet add package Serilog dotnet add package Npgsql --version 8.0.3 dotnet add package Microsoft.EntityFrameworkCore.Design Installing into a specific project from the solution root: dotnet add src/MyProduct.Api/MyProduct.Api.csproj package Serilog ------------------------------------------------------------------------------- REMOVING ------------------------------------------------------------------------------- dotnet remove package Serilog ------------------------------------------------------------------------------- LISTING ------------------------------------------------------------------------------- dotnet list package dotnet list package --outdated dotnet list package --vulnerable dotnet list package --deprecated ------------------------------------------------------------------------------- RESTORING ------------------------------------------------------------------------------- dotnet restore dotnet restore --force --no-cache ------------------------------------------------------------------------------- VERSION PINNING ------------------------------------------------------------------------------- Version numbers can be pinned exactly, or use a floating range. Central package version management (one place for all versions) uses a Directory.Packages.props file at the solution root: true ------------------------------------------------------------------------------- LOCAL PACKAGES ------------------------------------------------------------------------------- Point NuGet at a local folder of .nupkg files, useful for testing an unpublished package. dotnet nuget add source /home/user/local-packages --name LocalFeed dotnet add package MyLocalLib --source LocalFeed ------------------------------------------------------------------------------- PRIVATE FEEDS ------------------------------------------------------------------------------- Common in companies using Azure Artifacts, GitHub Packages, or a self-hosted feed like BaGet or ProGet. dotnet nuget add source https://nuget.pkg.github.com/OWNER/index.json \ --name github --username USERNAME --password TOKEN --store-password-in-clear-text Feeds are stored in NuGet.Config, either per-solution or at ~/.nuget/NuGet/NuGet.Config for the whole machine. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Mixing package versions across projects in the same solution without central package management, which leads to subtle binding redirect and "version conflict" warnings at build time. Committing NuGet.Config with a plaintext password from --store-password-in-clear-text into source control. Use environment variables or a credential provider instead in real projects. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know that PackageReference (modern, in the .csproj) replaced packages.config (legacy, XML file listing every package). Almost all new .NET projects use PackageReference. ================================================================================ VARIABLES AND TYPES ================================================================================ DESCRIPTION C# is statically typed. Every variable has a type known at compile time, either declared explicitly or inferred with var. The type system splits into value types (structs, stored inline / on the stack when local) and reference types (classes, stored on the heap with a reference on the stack). ------------------------------------------------------------------------------- DECLARING VARIABLES ------------------------------------------------------------------------------- int age = 30; string name = "Jake"; var total = 42; // type inferred as int const double Pi = 3.14159; // compile-time constant readonly int id; // set once, in constructor only ------------------------------------------------------------------------------- PRIMITIVE TYPES ------------------------------------------------------------------------------- bool true / false, 1 byte byte 0 to 255, unsigned 8-bit sbyte -128 to 127, signed 8-bit short -32,768 to 32,767, signed 16-bit ushort 0 to 65,535, unsigned 16-bit int -2,147,483,648 to 2,147,483,647, signed 32-bit uint 0 to 4,294,967,295, unsigned 32-bit long -9.2 quintillion to 9.2 quintillion, signed 64-bit ulong 0 to 18.4 quintillion, unsigned 64-bit float 32-bit IEEE floating point, ~6-9 digit precision double 64-bit IEEE floating point, ~15-17 digit precision decimal 128-bit, base-10, used for money, ~28-29 digit precision char a single UTF-16 code unit string an immutable sequence of chars ------------------------------------------------------------------------------- NULLABLE TYPES ------------------------------------------------------------------------------- Value types are non-nullable by default. Add ? to allow null. int? maybeAge = null; double? maybeScore = null; if (maybeAge.HasValue) { Console.WriteLine(maybeAge.Value); } int age = maybeAge ?? 0; // null-coalescing default int age2 = maybeAge ?? throw new ArgumentNullException(); Nullable reference types (enabled via enable in the .csproj) make the compiler warn when a reference type that is not marked with ? is assigned or passed a possible null. string? middleName = null; // explicitly nullable string firstName = "Jake"; // compiler assumes never null ------------------------------------------------------------------------------- REFERENCE TYPES VS VALUE TYPES ------------------------------------------------------------------------------- Value types (struct, int, bool, enum, DateTime, all primitives): - copied by value on assignment or when passed to a method - live on the stack when they are local variables, or inline inside whatever object contains them - default value is all-zero-bits, never null (unless Nullable) Reference types (class, string, array, delegate, interface): - assignment copies the reference, not the object - live on the heap, garbage collected - default value is null struct PointStruct { public int X, Y; } class PointClass { public int X, Y; } PointStruct a = new PointStruct { X = 1, Y = 2 }; PointStruct b = a; // b is a full independent copy b.X = 99; // a.X is still 1 PointClass c = new PointClass { X = 1, Y = 2 }; PointClass d = c; // d points at the same object as c d.X = 99; // c.X is now also 99 ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- Convert.ToInt32("42") int.Parse("42") int.TryParse("42", out int result) double.Parse("3.14", CultureInfo.InvariantCulture) value.ToString() value.GetType() typeof(int) default(int) // 0 default(string) // null ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- int? id = null; int safeId = id ?? -1; object boxed = 42; // boxing: value type -> heap int unboxed = (int)boxed; // unboxing: heap -> value type back int x = 5; int y = 10; (x, y) = (y, x); // tuple swap, no temp variable needed ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Comparing a nullable value type directly against a literal without checking HasValue, which silently works for == but can surprise people coming from other languages. Believing structs are always faster than classes. Large structs copied frequently (function parameters, list elements) can be slower than a class reference due to copy overhead. Keep structs small, typically 16 bytes or less as a rule of thumb. Forgetting that decimal, not double or float, is the correct type for money and other exact base-10 arithmetic. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be able to explain boxing and unboxing and why it is a performance concern (heap allocation, GC pressure) when value types are stored in a non-generic collection like ArrayList or passed as object. Be able to state why float/double are unsuitable for currency: binary floating point cannot represent most base-10 fractions exactly. ================================================================================ OPERATORS ================================================================================ DESCRIPTION C# supports the usual arithmetic, comparison, logical, and bitwise operators, plus several C#-specific null-handling and pattern operators that come up constantly in modern code. ------------------------------------------------------------------------------- ARITHMETIC ------------------------------------------------------------------------------- + - * / % ++ -- int a = 7 / 2; // 3, integer division truncates double b = 7.0 / 2; // 3.5 int c = 7 % 2; // 1, remainder ------------------------------------------------------------------------------- COMPARISON AND LOGICAL ------------------------------------------------------------------------------- == != < > <= >= && || ! Short-circuit evaluation applies to && and ||: the right side is not evaluated if the left side already determines the result. ------------------------------------------------------------------------------- BITWISE ------------------------------------------------------------------------------- & AND | OR ^ XOR ~ NOT (bitwise complement) << left shift >> right shift (arithmetic for signed types) >>> unsigned right shift (C# 11+) ------------------------------------------------------------------------------- NULL HANDLING OPERATORS ------------------------------------------------------------------------------- string? name = GetName(); string safe = name ?? "unknown"; // null-coalescing name ??= "default"; // null-coalescing assignment int? len = name?.Length; // null-conditional name?.Trim().ToUpper(); // chained null-conditional var first = list?[0]; // null-conditional indexer ------------------------------------------------------------------------------- OTHER OPERATORS ------------------------------------------------------------------------------- obj is SomeType // type check obj as SomeType // safe cast, null on failure (SomeType)obj // hard cast, throws on failure typeof(SomeType) nameof(someVariable) condition ? valueIfTrue : valueIfFalse // ternary x switch { 1 => "one", 2 => "two", _ => "other" } // switch expression ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- int mask = 0b_1010_1010; bool isEven = (n & 1) == 0; int doubled = n << 1; int halved = n >> 1; var description = age switch { < 13 => "child", < 20 => "teenager", < 65 => "adult", _ => "senior" }; ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using = instead of == inside a condition. C# will not compile this for bool conditions the way C does, but it still trips people up when porting logic mentally from other languages. Assuming (as) throws on failure. It returns null instead; a hard cast with parentheses is what throws an InvalidCastException. Forgetting operator precedence around bitwise operators mixed with comparisons, e.g. writing "if (x & 1 == 0)" instead of "if ((x & 1) == 0)" -- == binds tighter than &, changing the result. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Bit tricks come up often: n & (n - 1) clears the lowest set bit, n & -n isolates the lowest set bit, and (n & 1) == 0 checks evenness without using the modulo operator. ================================================================================ CONTROL FLOW ================================================================================ DESCRIPTION Covers if/else, switch statements and expressions, pattern matching, and every loop construct in C#. ------------------------------------------------------------------------------- IF / ELSE ------------------------------------------------------------------------------- if (score >= 90) { grade = "A"; } else if (score >= 80) { grade = "B"; } else { grade = "F"; } ------------------------------------------------------------------------------- SWITCH STATEMENT ------------------------------------------------------------------------------- switch (dayOfWeek) { case DayOfWeek.Saturday: case DayOfWeek.Sunday: isWeekend = true; break; default: isWeekend = false; break; } ------------------------------------------------------------------------------- SWITCH EXPRESSION (C# 8+) ------------------------------------------------------------------------------- string category = age switch { < 13 => "child", >= 13 and < 20 => "teen", >= 20 and < 65 => "adult", _ => "senior" }; ------------------------------------------------------------------------------- PATTERN MATCHING ------------------------------------------------------------------------------- Type patterns: if (shape is Circle c) { Console.WriteLine(c.Radius); } Property patterns: if (person is { Age: >= 18, Country: "US" }) { Console.WriteLine("Eligible"); } Positional patterns (with records): record Point(int X, int Y); static string Classify(Point p) => p switch { (0, 0) => "origin", (var x, 0) => $"on x-axis at {x}", (0, var y) => $"on y-axis at {y}", _ => "somewhere else" }; List patterns (C# 11+): int[] numbers = { 1, 2, 3 }; if (numbers is [1, 2, 3]) { Console.WriteLine("exact match"); } if (numbers is [var first, .., var last]) { Console.WriteLine($"{first} .. {last}"); } ------------------------------------------------------------------------------- LOOPS ------------------------------------------------------------------------------- for (int i = 0; i < 10; i++) { Console.WriteLine(i); } foreach (var item in collection) { Console.WriteLine(item); } int i = 0; while (i < 10) { Console.WriteLine(i); i++; } int j = 0; do { Console.WriteLine(j); j++; } while (j < 10); Loop control keywords: break exits the nearest enclosing loop or switch continue skips to the next iteration goto jumps to a labeled statement, rarely used, mostly for breaking out of nested loops when a boolean flag is awkward ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Modifying a collection while iterating it with foreach throws InvalidOperationException ("Collection was modified"). Iterate over a copy (ToList()) or use a for loop with an index if mutation during iteration is required. Forgetting break in a switch statement (not switch expression) causes a compile error in C#, unlike C/C++/Java where it silently falls through. C# only allows fallthrough between empty case labels. Off-by-one errors in for loops, especially when converting from 0-indexed to 1-indexed thinking mid-algorithm. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- List patterns and property patterns show up increasingly often in modern C# codebases and are worth being fluent in for interviews at companies on recent .NET versions. ================================================================================ METHODS AND PARAMETERS ================================================================================ DESCRIPTION Methods are the basic unit of behavior in C#. This section covers method declarations plus every parameter modifier: optional, named, ref, out, in, and params. ------------------------------------------------------------------------------- BASIC METHOD DECLARATION ------------------------------------------------------------------------------- public int Add(int a, int b) { return a + b; } public int Add(int a, int b) => a + b; // expression-bodied member ------------------------------------------------------------------------------- OPTIONAL PARAMETERS ------------------------------------------------------------------------------- public void Greet(string name, string greeting = "Hello") { Console.WriteLine($"{greeting}, {name}!"); } Greet("Jake"); // "Hello, Jake!" Greet("Jake", "Hey"); // "Hey, Jake!" Optional parameters must come after all required parameters. ------------------------------------------------------------------------------- NAMED PARAMETERS ------------------------------------------------------------------------------- void CreateUser(string name, int age, bool isAdmin = false) { } CreateUser(name: "Jake", age: 30, isAdmin: true); CreateUser(age: 30, name: "Jake"); // order does not matter when named ------------------------------------------------------------------------------- REF, OUT, IN, PARAMS ------------------------------------------------------------------------------- ref -- pass by reference, must be initialized before the call, the method can read and modify it, the change is visible to the caller. void Double(ref int x) { x *= 2; } int n = 5; Double(ref n); // n is now 10 out -- pass by reference specifically for returning a value, does not need to be initialized before the call, the method must assign it. bool TryParseAge(string input, out int age) { return int.TryParse(input, out age); } if (TryParseAge("30", out int result)) { Console.WriteLine(result); } in -- pass by reference but read-only inside the method, used to avoid copying large structs without allowing mutation. void Print(in Vector3 v) { Console.WriteLine(v.X); } params -- allows a variable number of arguments packed into an array. int Sum(params int[] numbers) { int total = 0; foreach (var n in numbers) total += n; return total; } Sum(1, 2, 3, 4); // any number of arguments Sum(); // zero is fine too ------------------------------------------------------------------------------- LOCAL FUNCTIONS ------------------------------------------------------------------------------- A function defined inside another function, useful for recursion helpers and avoiding polluting the class with private helpers. public int Factorial(int n) { return Helper(n); int Helper(int x) => x <= 1 ? 1 : x * Helper(x - 1); } ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting the ref/out keyword at the call site, not just the declaration. Both places must repeat the keyword. Overusing ref/out where a simple return value or tuple return would read more clearly. Modern C# often prefers returning a tuple over multiple out parameters. (bool success, int value) TryDivide(int a, int b) => b == 0 ? (false, 0) : (true, a / b); ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know the difference between ref and out clearly -- it is a frequent interview question. ref requires definite assignment before the call; out requires definite assignment before the method returns. ================================================================================ CLASSES ================================================================================ DESCRIPTION Classes are reference types and the primary way to model objects with state and behavior in C#. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public class Employee { public string Name { get; set; } public decimal Salary { get; private set; } public Employee(string name, decimal salary) { Name = name; Salary = salary; } public void GiveRaise(decimal amount) { Salary += amount; } } var e = new Employee("Jake", 90000m); ------------------------------------------------------------------------------- PROPERTIES ------------------------------------------------------------------------------- public class Point { public int X { get; set; } // auto property public int Y { get; init; } // init-only, C# 9+ public int Sum => X + Y; // computed property private int _z; public int Z // full property { get => _z; set => _z = value < 0 ? 0 : value; } } ------------------------------------------------------------------------------- CONSTRUCTORS ------------------------------------------------------------------------------- public class Person { public string Name { get; } public int Age { get; } public Person(string name) : this(name, 0) { } public Person(string name, int age) { Name = name; Age = age; } static Person() { // static constructor, runs once, before first use of the type } } Primary constructors (C# 12+): public class Person(string name, int age) { public string Name => name; public int Age => age; } ------------------------------------------------------------------------------- STATIC MEMBERS ------------------------------------------------------------------------------- public class Counter { public static int InstanceCount { get; private set; } public Counter() { InstanceCount++; } } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- obj.ToString() obj.Equals(other) obj.GetHashCode() obj.GetType() object.ReferenceEquals(a, b) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Overriding Equals without also overriding GetHashCode, which breaks dictionary and hash set behavior silently. Making every property auto-implemented with a public setter, even when the object should be immutable after construction. Prefer init or private set for anything not meant to change after creation. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain the difference between a field and a property, and why properties (even trivial auto-properties) are preferred for public API surface -- they allow adding logic later without a breaking binary change. ================================================================================ STRUCTS ================================================================================ DESCRIPTION Structs are value types, typically used for small, immutable, data- only aggregates where copy semantics and avoiding heap allocation matter (points, colors, money amounts, small coordinate pairs). ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public struct Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } } readonly struct ReadOnlyPoint // C# 7.2+, whole struct is immutable { public int X { get; } public int Y { get; } public ReadOnlyPoint(int x, int y) => (X, Y) = (x, y); } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- Structs implicitly inherit from System.ValueType, and get default Equals/GetHashCode based on field-by-field comparison unless you override them (recommended for performance-sensitive structs). ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Point a = new Point(1, 2); Point b = a; // full copy // mutating b never affects a ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Copying a struct is O(size of struct), which is why large structs (more than roughly 16-24 bytes as a common guideline) can become slower to pass around than a class reference (which is always a fixed 8 bytes on 64-bit). ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Making a large mutable struct with public setters and passing it around by value, leading to confusing bugs where a "modification" silently vanishes because it happened on a copy. Using a struct purely out of a belief that "structs are always faster." That is only true for small, short-lived, rarely-copied data. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know when to reach for a struct: small, immutable, value-semantics data that is created and destroyed often (e.g. inside a tight loop), where avoiding GC pressure from heap allocation matters. ================================================================================ RECORDS ================================================================================ DESCRIPTION Records (C# 9+) are reference types (or record structs, value types) designed for immutable data with built-in value-based equality, ToString, and non-destructive mutation via "with" expressions. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public record Point(int X, int Y); public record Person { public string Name { get; init; } public int Age { get; init; } } public record struct Coordinates(double Lat, double Lng); // value type ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- Records get, for free: - value-based Equals and GetHashCode (compares property values, not references) - a readable ToString(), e.g. "Point { X = 1, Y = 2 }" - a Deconstruct method for positional records - the "with" expression for non-destructive copies ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var p1 = new Point(1, 2); var p2 = new Point(1, 2); Console.WriteLine(p1 == p2); // True, value equality var p3 = p1 with { Y = 99 }; // copy with Y changed Console.WriteLine(p3); // Point { X = 1, Y = 99 } var (x, y) = p1; // deconstruction ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Expecting record equality on a record with mutable collection properties (like List) to compare contents deeply. Value equality on records compares each property using its own Equals, and List.Equals is reference equality, so two records holding "equal-looking" lists will not compare equal. Using a plain record (reference type) when a record struct would avoid heap allocation for small, frequently created data. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Records are the idiomatic modern choice for DTOs and request/response models in ASP.NET Core APIs because of immutability and free value equality, which makes tests and caching keys much simpler to reason about than classes with mutable properties. ================================================================================ ENUMS ================================================================================ DESCRIPTION Enums define a named set of integral constants. They compile down to an underlying integral type (int by default). ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public enum OrderStatus { Pending, // 0 Shipped, // 1 Delivered, // 2 Cancelled // 3 } public enum HttpStatusGroup : byte { Informational = 1, Success = 2, Redirection = 3, ClientError = 4, ServerError = 5 } [Flags] public enum Permissions { None = 0, Read = 1 << 0, Write = 1 << 1, Execute = 1 << 2, All = Read | Write | Execute } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- Enum.GetValues() Enum.GetNames() Enum.Parse("Shipped") Enum.TryParse("Shipped", out var status) status.ToString() status.HasFlag(Permissions.Write) ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var status = OrderStatus.Shipped; Console.WriteLine((int)status); // 1 Console.WriteLine(status.ToString()); // "Shipped" var perms = Permissions.Read | Permissions.Write; Console.WriteLine(perms.HasFlag(Permissions.Write)); // True Console.WriteLine(perms); // "Read, Write" ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Storing enum values as strings in a database and then reordering or renaming enum members, which silently breaks stored data. Prefer storing the underlying int (with a comment mapping values) or a string that matches Enum.Parse exactly, and never reorder existing members. Forgetting [Flags] on a bitmask-style enum, which makes ToString() print the raw number instead of a readable combination. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be able to explain why inserting a new enum value in the middle of an existing enum (rather than appending at the end) is dangerous for anything persisted by its integer value. ================================================================================ INTERFACES ================================================================================ DESCRIPTION An interface defines a contract of members that an implementing type must provide. C# supports multiple interface implementation (unlike multiple class inheritance, which is not allowed). ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public interface IShape { double Area(); double Perimeter(); } public interface ILogger { void Log(string message); void LogError(string message) => Log($"ERROR: {message}"); // default // interface // method, // C# 8+ } public class Circle : IShape { public double Radius { get; init; } public double Area() => Math.PI * Radius * Radius; public double Perimeter() => 2 * Math.PI * Radius; } ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- IShape shape = new Circle { Radius = 2 }; Console.WriteLine(shape.Area()); Explicit interface implementation (used when a class implements two interfaces with a colliding member name): public interface IEnglishGreeter { string Greet(); } public interface ISpanishGreeter { string Greet(); } public class Bilingual : IEnglishGreeter, ISpanishGreeter { string IEnglishGreeter.Greet() => "Hello"; string ISpanishGreeter.Greet() => "Hola"; } Bilingual b = new(); string en = ((IEnglishGreeter)b).Greet(); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Adding a non-default member to a widely implemented interface, which breaks every implementing class until they add the new member. Use default interface methods to add new members without breaking existing implementers. Overusing interfaces for types that only ever have one implementation "just in case," adding indirection without real benefit. Prefer introducing an interface when a second implementation, or a test double, actually shows up. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to discuss interface segregation (many small, focused interfaces rather than one large one) and how interfaces enable dependency inversion and mocking in unit tests. ================================================================================ INHERITANCE AND POLYMORPHISM ================================================================================ DESCRIPTION C# supports single class inheritance. Polymorphism is achieved through virtual/override members and interfaces. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public abstract class Animal { public string Name { get; } protected Animal(string name) => Name = name; public abstract string MakeSound(); public virtual string Describe() => $"{Name} says {MakeSound()}"; } public class Dog : Animal { public Dog(string name) : base(name) { } public override string MakeSound() => "Woof"; } public class Puppy : Dog { public Puppy(string name) : base(name) { } public override string Describe() => base.Describe() + " (softly)"; } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- base.SomeMethod() // call the parent implementation sealed override Method() // prevent further overriding new Method() // hide, not override, a base member ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Animal a = new Dog("Rex"); Console.WriteLine(a.Describe()); // "Rex says Woof" // MakeSound is resolved at runtime based on the actual object type, // not the declared variable type -- this is dynamic dispatch. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using "new" to hide a base member instead of "override," which silently breaks polymorphism -- calling the method through a base class reference invokes the base implementation, not the derived one, which surprises almost everyone the first time they hit it. Deep inheritance chains (more than 2-3 levels) that become hard to reason about. Favor composition over inheritance once a hierarchy starts feeling forced. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain "new" vs "override" with a concrete example, since it is one of the most commonly asked C#-specific OOP questions. Be ready to discuss "favor composition over inheritance" and give a real example of when you chose one over the other. ================================================================================ GENERICS ================================================================================ DESCRIPTION Generics let a type or method be parameterized over a type, avoiding both code duplication and the boxing/casting overhead of using object for a general-purpose container. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public class Box { public T Value { get; set; } } public class Repository where TEntity : class { public TEntity? GetById(TKey id) => default; } public T Max(T a, T b) where T : IComparable { return a.CompareTo(b) > 0 ? a : b; } ------------------------------------------------------------------------------- GENERIC CONSTRAINTS ------------------------------------------------------------------------------- where T : class // reference type where T : struct // value type where T : new() // has a public parameterless constructor where T : SomeBaseClass // must derive from SomeBaseClass where T : ISomeInterface // must implement ISomeInterface where T : notnull // cannot be a nullable type where T : U // T must derive from another type param U ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- typeof(List<>) typeof(T) default(T) ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var intBox = new Box { Value = 42 }; var stringBox = new Box { Value = "hi" }; int bigger = Max(3, 7); string laterAlphabetically = Max("apple", "banana"); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using object instead of a generic parameter for a "general purpose" container, forcing casts and boxing at every call site. Forgetting a constraint and then trying to call a method (like CompareTo, or a constructor) that only exists on some possible types T could be. The compiler will refuse, and the fix is almost always adding the appropriate where clause. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know the difference between covariance (out T, e.g. IEnumerable) and contravariance (in T, e.g. IComparer) for generic interfaces, and why arrays are covariant but not type-safe at runtime as a result. ================================================================================ DELEGATES AND EVENTS ================================================================================ DESCRIPTION A delegate is a type-safe function pointer. Events are a publish/subscribe wrapper around delegates that restricts who can invoke the handlers. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public delegate int Operation(int a, int b); Operation add = (a, b) => a + b; Operation multiply = (a, b) => a * b; Built-in generic delegates, used far more often than custom ones: Func add2 = (a, b) => a + b; // has a return value Action log = msg => Console.WriteLine(msg); // no return value Predicate isEven = n => n % 2 == 0; // returns bool Events: public class Button { public event EventHandler? Clicked; public void SimulateClick() { Clicked?.Invoke(this, EventArgs.Empty); } } var button = new Button(); button.Clicked += (sender, args) => Console.WriteLine("Clicked!"); button.SimulateClick(); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- someDelegate.Invoke(args) someDelegate?.Invoke(args) // null-safe invoke someDelegate += anotherHandler // multicast, add someDelegate -= aHandler // multicast, remove someDelegate.GetInvocationList() ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Multicast delegate calling multiple handlers in order added: Action greet = () => Console.WriteLine("Hi"); greet += () => Console.WriteLine("Hello"); greet(); // prints both lines ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Invoking an event without a null check when there are no subscribers, which throws NullReferenceException. Always use ?.Invoke() or check for null first. Forgetting to unsubscribe long-lived event handlers, which is a common source of memory leaks: the publisher holds a reference to the subscriber through the delegate, keeping it alive. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain why events use += / -= rather than direct assignment (=), and how that protects external code from wiping out other subscribers' handlers. ================================================================================ EXTENSION METHODS ================================================================================ DESCRIPTION Extension methods add new methods to an existing type, including types you do not own (like string or List), without subclassing or modifying the original type. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public static class StringExtensions { public static bool IsNullOrBlank(this string? value) => string.IsNullOrWhiteSpace(value); public static string Truncate(this string value, int maxLength) => value.Length <= maxLength ? value : value[..maxLength] + "..."; } Must be declared in a static class, and the first parameter must be marked with "this". ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- string? name = null; bool blank = name.IsNullOrBlank(); // works even though name is null string bio = "A very long bio that goes on and on"; string preview = bio.Truncate(10); // "A very lon..." ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Overusing extension methods for logic that really belongs as a regular method on a type you own, scattering related behavior across unrelated static classes and making it harder to discover via IntelliSense/autocomplete on the actual type. Forgetting to import the namespace containing the static class, which makes the extension method invisible at the call site with no obvious error message pointing at "add a using statement." ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Nearly all of LINQ (Where, Select, OrderBy, etc.) is implemented as extension methods on IEnumerable in System.Linq -- a good example to cite when asked "what are extension methods used for in practice." ================================================================================ EXCEPTION HANDLING ================================================================================ DESCRIPTION C# uses structured exception handling with try/catch/finally. All exceptions derive from System.Exception. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- try { int result = 10 / divisor; } catch (DivideByZeroException ex) { Console.WriteLine($"Cannot divide by zero: {ex.Message}"); } catch (Exception ex) when (ex.Message.Contains("critical")) { // filtered catch, only runs when the condition is true } finally { Console.WriteLine("Always runs, even if an exception was thrown"); } Custom exceptions: public class InsufficientFundsException : Exception { public decimal Shortfall { get; } public InsufficientFundsException(decimal shortfall) : base($"Short by {shortfall:C}") { Shortfall = shortfall; } } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- throw new SomeException("message"); throw; // re-throw, preserves stack trace throw ex; // re-throw, resets stack trace (avoid) ex.Message ex.StackTrace ex.InnerException ex.Data ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- try { DoSomethingRisky(); } catch (IOException ex) { LogError(ex); throw; // preserves original stack trace for the caller } ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using "throw ex;" instead of "throw;" inside a catch block, which resets the stack trace and makes production debugging much harder. Catching Exception broadly and swallowing it silently (empty catch block), hiding real bugs. Catch the specific exception type you can actually handle, and let the rest propagate. Using exceptions for ordinary control flow (e.g. parsing user input with int.Parse wrapped in try/catch instead of int.TryParse), which is slower and reads worse than the non-throwing alternative. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain "throw;" vs "throw ex;" precisely -- it is one of the most common C#-specific interview questions on exception handling. Know that exception filters (catch (Exception ex) when (condition)) let you inspect an exception without unwinding the stack if the condition is false, unlike catching and rethrowing manually. ================================================================================ IDISPOSABLE AND USING ================================================================================ DESCRIPTION IDisposable is the standard pattern for deterministic cleanup of unmanaged resources (file handles, database connections, network sockets) that the garbage collector cannot reliably clean up on its own timeline. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public class FileLogger : IDisposable { private readonly StreamWriter _writer; private bool _disposed; public FileLogger(string path) { _writer = new StreamWriter(path, append: true); } public void Log(string message) => _writer.WriteLine(message); public void Dispose() { if (_disposed) return; _writer.Dispose(); _disposed = true; GC.SuppressFinalize(this); } } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- using var logger = new FileLogger("log.txt"); // C# 8+ using declaration using (var logger2 = new FileLogger("log2.txt")) { } // classic using block await using var conn = new SomeAsyncDisposable(); // for IAsyncDisposable ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- using (var connection = new SqlConnection(connectionString)) { connection.Open(); // Dispose() called automatically here, even if an exception // is thrown inside the block } using var logger = new FileLogger("app.log"); logger.Log("Started"); // Dispose() called automatically at the end of the enclosing scope ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting to wrap a disposable resource (DbConnection, StreamReader, HttpClient response body, etc.) in a using statement, leaking unmanaged handles until the finalizer eventually runs, if ever. Creating a new HttpClient per request instead of reusing one long- lived instance (or IHttpClientFactory) -- HttpClient is intended to be reused, and disposing/recreating it repeatedly can exhaust sockets under load (socket exhaustion). ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain the Dispose pattern including the disposed flag check and GC.SuppressFinalize, and why deterministic disposal matters even though .NET has a garbage collector. ================================================================================ STRINGS ================================================================================ DESCRIPTION Strings in C# are immutable sequences of UTF-16 chars. Every "mutating" string operation actually returns a new string. For heavy concatenation in a loop, use StringBuilder instead. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- string a = "hello"; string b = new string('x', 5); // "xxxxx" string c = string.Empty; string? d = null; string multi = """ This is a raw string literal (C# 11+). No escaping needed for quotes or backslashes. """; ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- s.Length s.ToUpper() / s.ToLower() s.Trim() / s.TrimStart() / s.TrimEnd() s.Substring(start, length) s[start..end] // range indexer, C# 8+ s.Split(',') s.Split(',', StringSplitOptions.RemoveEmptyEntries) string.Join(", ", items) s.Replace("old", "new") s.Contains("sub") s.StartsWith("prefix") / s.EndsWith("suffix") s.IndexOf("sub") / s.LastIndexOf("sub") string.IsNullOrEmpty(s) string.IsNullOrWhiteSpace(s) s.PadLeft(10) / s.PadRight(10) s.ToCharArray() s.Equals(other, StringComparison.OrdinalIgnoreCase) string.Compare(a, b) s.Reverse() // returns IEnumerable, needs new string(...) ------------------------------------------------------------------------------- STRINGBUILDER ------------------------------------------------------------------------------- var sb = new StringBuilder(); sb.Append("Hello"); sb.Append(' ').Append("World"); sb.AppendLine("!"); sb.Insert(0, ">> "); sb.Replace("World", "C#"); sb.Remove(0, 3); string result = sb.ToString(); sb.Clear(); ------------------------------------------------------------------------------- FORMATTING AND INTERPOLATION ------------------------------------------------------------------------------- string name = "Jake"; int age = 30; string a = $"{name} is {age} years old"; string b = $"{name,10}"; // right-align in 10 chars string c = $"{price:C}"; // currency format string d = $"{value:N2}"; // number, 2 decimals string e = $"{date:yyyy-MM-dd}"; string f = $"{ratio:P1}"; // percentage, 1 decimal string g = string.Format("{0} is {1}", name, age); Interpolated raw string with embedded expressions (C# 11+): string h = $""" Name: {name} Age: {age} """; ------------------------------------------------------------------------------- REGEX ------------------------------------------------------------------------------- using System.Text.RegularExpressions; bool isMatch = Regex.IsMatch(input, @"^\d{3}-\d{4}$"); Match m = Regex.Match(input, @"(\d+)-(\d+)"); if (m.Success) { string first = m.Groups[1].Value; } MatchCollection all = Regex.Matches(input, @"\d+"); string replaced = Regex.Replace(input, @"\s+", " "); string[] parts = Regex.Split(input, @",\s*"); Source-generated regex (fast, compile-time, C# 13/.NET 9+): public partial class PhoneMatcher { [GeneratedRegex(@"^\d{3}-\d{4}$")] public static partial Regex PhonePattern(); } ------------------------------------------------------------------------------- PARSING ------------------------------------------------------------------------------- int n = int.Parse("42"); bool ok = int.TryParse("42", out int result); double d = double.Parse("3.14", CultureInfo.InvariantCulture); DateTime dt = DateTime.Parse("2024-01-15"); DateTime.TryParseExact("15/01/2024", "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact); ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Reverse a string: char[] chars = s.ToCharArray(); Array.Reverse(chars); string reversed = new string(chars); Check palindrome, ignoring case and non-alphanumeric: static bool IsPalindrome(string s) { int left = 0, right = s.Length - 1; while (left < right) { if (!char.IsLetterOrDigit(s[left])) { left++; continue; } if (!char.IsLetterOrDigit(s[right])) { right--; continue; } if (char.ToLower(s[left]) != char.ToLower(s[right])) return false; left++; right--; } return true; } Character frequency count: var freq = new Dictionary(); foreach (char c in s) { freq[c] = freq.GetValueOrDefault(c) + 1; } Anagram check via sorted chars: static bool IsAnagram(string a, string b) { if (a.Length != b.Length) return false; var ca = a.ToCharArray(); Array.Sort(ca); var cb = b.ToCharArray(); Array.Sort(cb); return new string(ca) == new string(cb); } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Length O(1) Indexing s[i] O(1) Concatenation s + t O(n + m), new string allocated Substring O(k), k = length of the substring Contains / IndexOf O(n) worst case StringBuilder.Append amortized O(1) per append ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Concatenating strings with + inside a loop, which allocates a new string on every iteration -- O(n^2) total for n appends. Use StringBuilder instead. Using == for culture-sensitive comparisons without specifying StringComparison, which can behave unexpectedly across locales (e.g. Turkish "I" casing issues). Prefer StringComparison.Ordinal or OrdinalIgnoreCase for exact/technical comparisons, and CurrentCulture only when comparing user-facing text. Forgetting that strings are immutable, so s.Trim() alone does nothing unless the result is assigned back: s = s.Trim(); ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know why string concatenation in a loop is a classic performance anti-pattern and be ready to explain StringBuilder's internal resizable buffer as the fix. Sliding window and two-pointer string problems (longest substring without repeating characters, valid palindrome, anagram grouping) are extremely common interview questions -- see the STRINGS, SLIDING WINDOW, and TWO POINTERS sections together. ================================================================================ ARRAY ================================================================================ DESCRIPTION A fixed-size, zero-indexed collection of elements of the same type, allocated as one contiguous block of memory. The size cannot change after creation. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- int[] nums = new int[5]; int[] nums2 = { 1, 2, 3, 4, 5 }; int[] nums3 = new int[] { 1, 2, 3 }; string[] names = new string[3]; int[,] grid = new int[3, 3]; // 2D rectangular array int[,] grid2 = { { 1, 2 }, { 3, 4 } }; int[][] jagged = new int[3][]; // jagged array jagged[0] = new int[] { 1, 2 }; jagged[1] = new int[] { 3, 4, 5 }; ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- nums.Length grid.GetLength(0) / grid.GetLength(1) Array.Sort(nums) Array.Reverse(nums) Array.IndexOf(nums, 3) Array.Copy(source, destination, length) Array.Clear(nums) Array.Fill(nums, 0) Array.Resize(ref nums, 10) // creates a new array Array.BinarySearch(nums, 3) // array must be sorted nums.Clone() nums[1..3] // range indexer, new array ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- int[] nums = { 5, 3, 8, 1 }; Array.Sort(nums); // { 1, 3, 5, 8 } for (int row = 0; row < grid.GetLength(0); row++) { for (int col = 0; col < grid.GetLength(1); col++) { Console.Write(grid[row, col] + " "); } } ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- In-place reverse: static void Reverse(int[] arr) { int left = 0, right = arr.Length - 1; while (left < right) { (arr[left], arr[right]) = (arr[right], arr[left]); left++; right--; } } Rotate array right by k: static void Rotate(int[] nums, int k) { k %= nums.Length; Array.Reverse(nums); Array.Reverse(nums, 0, k); Array.Reverse(nums, k, nums.Length - k); } Prefix sum: int[] prefix = new int[nums.Length + 1]; for (int i = 0; i < nums.Length; i++) prefix[i + 1] = prefix[i] + nums[i]; // sum of nums[i..j] inclusive == prefix[j + 1] - prefix[i] ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Access by index O(1) Search (unsorted) O(n) Search (sorted, binary) O(log n) Insert / delete O(n) -- requires shifting or a new array Array.Sort O(n log n), introspective sort ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Trying to "add" or "remove" an element from a fixed array directly. Arrays cannot resize; use Array.Resize (which reallocates and copies) or switch to List if the size is not fixed in advance. Confusing a jagged array (int[][], each row can be a different length, rows are separate array objects) with a rectangular multidimensional array (int[,], one contiguous block, all rows the same length). They have different syntax and different performance characteristics -- jagged arrays are usually faster for row access. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Arrays are the backbone of almost every classic algorithm problem. Be fluent with in-place manipulation, since many interview questions explicitly ask for O(1) extra space. ================================================================================ LIST ================================================================================ DESCRIPTION List is a dynamically resizable array, the default general-purpose collection in C# for ordered data whose size changes over time. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- var list = new List(); var list2 = new List { 1, 2, 3 }; var list3 = new List(capacity: 100); var list4 = Enumerable.Range(1, 10).ToList(); var list5 = existingArray.ToList(); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- list.Add(4) list.AddRange(otherList) list.Insert(0, 99) list.Remove(3) // removes first matching value list.RemoveAt(0) list.RemoveAll(x => x < 0) list.Contains(3) list.IndexOf(3) list.Sort() list.Sort((a, b) => b.CompareTo(a)) // custom comparer, descending list.Reverse() list.Count list.Clear() list[0] // indexer list.Find(x => x > 5) list.FindAll(x => x > 5) list.Exists(x => x > 5) list.ForEach(x => Console.WriteLine(x)) list.ToArray() list.GetRange(0, 3) list.BinarySearch(3) // list must be sorted ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var names = new List { "Bob", "Alice", "Carl" }; names.Sort(); // alphabetical in place names.Add("Dave"); names.RemoveAt(0); var evens = names.Where(n => n.Length == 3).ToList(); ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Building a result list while iterating, very common in problems asking to "return all valid combinations": var results = new List>(); var current = new List(); // ... backtracking logic pushes/pops from current ... results.Add(new List(current)); // copy, not a reference! ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Access by index O(1) Add at end amortized O(1) Insert / remove at front O(n) Insert / remove at index O(n) Contains / IndexOf O(n) Sort O(n log n) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Adding "current" directly to "results" during backtracking instead of a copy (new List(current)). Since current is mutated afterward, every reference in results silently points at the same, later-changed list. Using RemoveAt or Remove inside a foreach loop over the same list, which throws InvalidOperationException. Iterate backwards with a for loop, or use RemoveAll, when removing conditionally. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- List is backed by an array that doubles in capacity when it fills up -- know this when asked to explain "amortized O(1)" append. ================================================================================ LINKEDLIST ================================================================================ DESCRIPTION LinkedList is a doubly-linked list. Rarely the right default choice compared to List, but useful when frequent insertion or removal happens in the middle of the sequence and you already hold a node reference. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- var list = new LinkedList(); var list2 = new LinkedList(new[] { 1, 2, 3 }); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- list.AddFirst(0) list.AddLast(4) list.AddBefore(node, 99) list.AddAfter(node, 100) list.Remove(3) list.RemoveFirst() list.RemoveLast() list.Find(3) // returns LinkedListNode list.First / list.Last // LinkedListNode node.Value node.Next / node.Previous list.Count ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var ll = new LinkedList(); ll.AddLast(1); ll.AddLast(2); var node = ll.AddLast(3); ll.AddAfter(node, 4); // 1 -> 2 -> 3 -> 4 for (var n = ll.First; n != null; n = n.Next) { Console.Write(n.Value + " "); } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Add/remove at a known node O(1) Add/remove at front/back O(1) Find by value O(n) Access by index not supported directly, O(n) to walk to it ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Reaching for LinkedList by habit from other languages when List would perform better in practice for almost all real-world workloads, since List has better cache locality even though its Big-O for middle insertion is worse. Note: classic "singly-linked list" LeetCode problems (reverse a linked list, detect a cycle, merge two sorted lists) are usually defined with a custom ListNode class in interviews, not System.Collections.Generic.LinkedList. Know both. public class ListNode { public int val; public ListNode? next; public ListNode(int val = 0, ListNode? next = null) { this.val = val; this.next = next; } } ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Reverse a singly linked list: static ListNode? Reverse(ListNode? head) { ListNode? prev = null; while (head != null) { var next = head.next; head.next = prev; prev = head; head = next; } return prev; } Detect a cycle (Floyd's tortoise and hare): static bool HasCycle(ListNode? head) { var slow = head; var fast = head; while (fast?.next != null) { slow = slow!.next; fast = fast.next.next; if (slow == fast) return true; } return false; } ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know Floyd's cycle detection cold -- it appears constantly, both as its own question and as a building block for "find the start of the cycle" and "find the middle of a list" problems. ================================================================================ DICTIONARY ================================================================================ DESCRIPTION Dictionary is a hash table mapping unique keys to values, the default C# collection for O(1) average lookups. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- var dict = new Dictionary(); var dict2 = new Dictionary { ["Alice"] = 30, ["Bob"] = 25 }; var dict3 = new Dictionary(StringComparer.OrdinalIgnoreCase); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- dict["Alice"] = 31; // add or overwrite dict.Add("Carl", 40); // throws if key exists dict.TryAdd("Carl", 40); // false if key exists, no throw dict.TryGetValue("Alice", out int age); dict.ContainsKey("Alice"); dict.ContainsValue(30); dict.Remove("Bob"); dict.Remove("Bob", out int removedValue); dict.Keys dict.Values dict.Count dict.GetValueOrDefault("Dave", 0); dict.Clear(); Iteration: foreach (var kvp in dict) { Console.WriteLine($"{kvp.Key}: {kvp.Value}"); } foreach (var (key, value) in dict) // deconstruction, C# 7+ { Console.WriteLine($"{key}: {value}"); } ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var wordCount = new Dictionary(); foreach (var word in words) { wordCount[word] = wordCount.GetValueOrDefault(word) + 1; } ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Two Sum: static int[] TwoSum(int[] nums, int target) { var seen = new Dictionary(); // value -> index for (int i = 0; i < nums.Length; i++) { int complement = target - nums[i]; if (seen.TryGetValue(complement, out int j)) { return new[] { j, i }; } seen[nums[i]] = i; } return Array.Empty(); } Group anagrams by sorted-char key: var groups = new Dictionary>(); foreach (var word in words) { var chars = word.ToCharArray(); Array.Sort(chars); var key = new string(chars); if (!groups.TryGetValue(key, out var list)) { list = new List(); groups[key] = list; } list.Add(word); } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Add / lookup / remove average O(1), worst case O(n) with heavy hash collisions Iteration O(n) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using dict[key] to read a possibly-missing key, which throws KeyNotFoundException. Use TryGetValue or GetValueOrDefault instead. Using a mutable object as a dictionary key without overriding Equals/GetHashCode consistently, which breaks lookups after the object's state changes. Modifying a dictionary's keys while iterating it, which throws InvalidOperationException just like with List. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Dictionary-based counting/lookup is probably the single most common tool in interview problem solving -- know GetValueOrDefault and TryGetValue cold, they come up in nearly every "frequency count" or "seen before" style problem. ================================================================================ HASHSET ================================================================================ DESCRIPTION HashSet stores unique elements with average O(1) add, remove, and contains checks. Also supports classic set algebra (union, intersection, difference). ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- var set = new HashSet(); var set2 = new HashSet { 1, 2, 3 }; var set3 = new HashSet(StringComparer.OrdinalIgnoreCase); var set4 = existingList.ToHashSet(); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- set.Add(4) // returns bool, false if already present set.Remove(2) set.Contains(3) set.Count set.UnionWith(otherSet) set.IntersectWith(otherSet) set.ExceptWith(otherSet) set.SymmetricExceptWith(otherSet) set.IsSubsetOf(otherSet) set.IsSupersetOf(otherSet) set.Overlaps(otherSet) set.SetEquals(otherSet) ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var seen = new HashSet(); foreach (var n in nums) { if (!seen.Add(n)) { Console.WriteLine($"Duplicate found: {n}"); } } ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Contains Duplicate: static bool ContainsDuplicate(int[] nums) => nums.Length != nums.Distinct().Count(); Longest Consecutive Sequence: static int LongestConsecutive(int[] nums) { var set = new HashSet(nums); int longest = 0; foreach (var n in set) { if (!set.Contains(n - 1)) // start of a sequence { int length = 1; while (set.Contains(n + length)) length++; longest = Math.Max(longest, length); } } return longest; } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Add / remove / contains average O(1) UnionWith / IntersectWith O(n + m) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Reaching for List.Contains() in a loop to check for duplicates or membership, which is O(n) per check (O(n^2) total). A HashSet turns the same check into O(1) average. Forgetting that HashSet does not preserve insertion order (that guarantee belongs to LinkedHashSet-style structures in other languages, not .NET's HashSet). ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- "Have you seen this before" style problems (duplicates, visited nodes in graph traversal, deduping) are the classic use case -- pair this section mentally with DICTIONARY and GRAPHS. ================================================================================ QUEUE ================================================================================ DESCRIPTION Queue is a first-in-first-out (FIFO) collection, the standard tool for breadth-first search and any producer/consumer-style ordering. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- var queue = new Queue(); var queue2 = new Queue(new[] { 1, 2, 3 }); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- queue.Enqueue(4) queue.Dequeue() // removes and returns the front queue.Peek() // returns the front without removing queue.TryDequeue(out int value) queue.TryPeek(out int value) queue.Count queue.Contains(3) queue.Clear() ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var q = new Queue(); q.Enqueue(1); q.Enqueue(2); q.Enqueue(3); Console.WriteLine(q.Dequeue()); // 1 Console.WriteLine(q.Peek()); // 2 ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Breadth-first search on a graph: static void Bfs(Dictionary> graph, int start) { var visited = new HashSet { start }; var queue = new Queue(); queue.Enqueue(start); while (queue.Count > 0) { int node = queue.Dequeue(); Console.WriteLine(node); foreach (var neighbor in graph[node]) { if (visited.Add(neighbor)) { queue.Enqueue(neighbor); } } } } Level-order traversal of a binary tree, level by level: var queue = new Queue(); queue.Enqueue(root); while (queue.Count > 0) { int levelSize = queue.Count; for (int i = 0; i < levelSize; i++) { var node = queue.Dequeue(); Console.Write(node.val + " "); if (node.left != null) queue.Enqueue(node.left); if (node.right != null) queue.Enqueue(node.right); } } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Enqueue / Dequeue / Peek O(1) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Calling Dequeue() on an empty queue, which throws InvalidOperationException. Check queue.Count > 0 first, or use TryDequeue. Confusing Queue (FIFO) with Stack (LIFO) under pressure -- remember "queue" like a line at a store, first person in line is served first. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Queue is essentially mandatory for BFS. If a problem asks for "shortest path" or "minimum steps" in an unweighted graph or grid, BFS with a Queue is almost always the answer. ================================================================================ STACK ================================================================================ DESCRIPTION Stack is a last-in-first-out (LIFO) collection, used for backtracking, expression parsing, undo history, and depth-first search implemented iteratively. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- var stack = new Stack(); var stack2 = new Stack(new[] { 1, 2, 3 }); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- stack.Push(4) stack.Pop() // removes and returns the top stack.Peek() // returns the top without removing stack.TryPop(out int value) stack.TryPeek(out int value) stack.Count stack.Contains(3) stack.Clear() ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var s = new Stack(); s.Push(1); s.Push(2); s.Push(3); Console.WriteLine(s.Pop()); // 3 Console.WriteLine(s.Peek()); // 2 ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Valid Parentheses: static bool IsValid(string s) { var stack = new Stack(); var pairs = new Dictionary { [')'] = '(', [']'] = '[', ['}'] = '{' }; foreach (char c in s) { if (c == '(' || c == '[' || c == '{') { stack.Push(c); } else if (pairs.ContainsKey(c)) { if (stack.Count == 0 || stack.Pop() != pairs[c]) return false; } } return stack.Count == 0; } Monotonic stack, Daily Temperatures (next warmer day): static int[] DailyTemperatures(int[] temps) { var result = new int[temps.Length]; var stack = new Stack(); // stores indices for (int i = 0; i < temps.Length; i++) { while (stack.Count > 0 && temps[i] > temps[stack.Peek()]) { int idx = stack.Pop(); result[idx] = i - idx; } stack.Push(i); } return result; } Iterative DFS on a graph: var stack = new Stack(); stack.Push(start); var visited = new HashSet(); while (stack.Count > 0) { int node = stack.Pop(); if (!visited.Add(node)) continue; Console.WriteLine(node); foreach (var neighbor in graph[node]) { if (!visited.Contains(neighbor)) stack.Push(neighbor); } } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Push / Pop / Peek O(1) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Calling Pop() or Peek() on an empty stack, which throws InvalidOperationException. Check stack.Count > 0 first, or use TryPop/TryPeek. Forgetting that .NET's Stack enumerates top-to-bottom (LIFO order), which surprises people expecting insertion order when they foreach directly over it. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- "Monotonic stack" is a named pattern worth memorizing by name: keep the stack's contents increasing or decreasing, popping whenever the invariant would break, to solve "next greater/smaller element" style problems in O(n) instead of O(n^2). ================================================================================ PRIORITY QUEUE ================================================================================ DESCRIPTION PriorityQueue (added in .NET 6) is a binary-heap based collection that always dequeues the element with the lowest priority value first (a min-heap by default). ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- var pq = new PriorityQueue(); var pq2 = new PriorityQueue( Comparer.Create((a, b) => b.CompareTo(a))); // max-heap version ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- pq.Enqueue(item, priority) pq.Dequeue() // removes and returns lowest-priority item pq.Peek() // views lowest-priority item without removing pq.TryDequeue(out var item, out var priority) pq.TryPeek(out var item, out var priority) pq.Count pq.EnqueueRange(items) pq.Clear() ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var pq = new PriorityQueue(); pq.Enqueue("low priority task", 5); pq.Enqueue("urgent task", 1); pq.Enqueue("medium task", 3); while (pq.Count > 0) { Console.WriteLine(pq.Dequeue()); } // urgent task, medium task, low priority task ------------------------------------------------------------------------------- COMMON LEETCODE PATTERNS ------------------------------------------------------------------------------- Kth largest element using a min-heap of size k: static int FindKthLargest(int[] nums, int k) { var pq = new PriorityQueue(); foreach (var n in nums) { pq.Enqueue(n, n); if (pq.Count > k) pq.Dequeue(); } return pq.Peek(); } Dijkstra's shortest path: static int[] Dijkstra(Dictionary> graph, int start, int nodeCount) { var dist = new int[nodeCount]; Array.Fill(dist, int.MaxValue); dist[start] = 0; var pq = new PriorityQueue(); pq.Enqueue(start, 0); while (pq.Count > 0) { int node = pq.Dequeue(); foreach (var (to, weight) in graph[node]) { int newDist = dist[node] + weight; if (newDist < dist[to]) { dist[to] = newDist; pq.Enqueue(to, newDist); } } } return dist; } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Enqueue O(log n) Dequeue O(log n) Peek O(1) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting PriorityQueue is a min-heap by default. For a max-heap, either negate the priority values or supply a reversed IComparer at construction. Expecting stable ordering among equal-priority elements. .NET's PriorityQueue does not guarantee FIFO order for ties. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know the "keep a heap of size k" trick for top-k / kth-largest problems -- it turns an O(n log n) full sort into O(n log k). Dijkstra's algorithm is the classic priority-queue interview question for weighted shortest-path problems; contrast it with plain BFS, which only works for unweighted graphs. ================================================================================ LINQ ================================================================================ DESCRIPTION Language Integrated Query. A set of extension methods over IEnumerable (and IQueryable for databases) that lets you filter, project, sort, and aggregate data declaratively, either with method syntax or query syntax. ------------------------------------------------------------------------------- METHOD SYNTAX VS QUERY SYNTAX ------------------------------------------------------------------------------- var adults = people.Where(p => p.Age >= 18).OrderBy(p => p.Name); var adults2 = from p in people where p.Age >= 18 orderby p.Name select p; Both compile to the same thing. Method syntax is far more common in real codebases; query syntax reads better for complex joins. ------------------------------------------------------------------------------- FILTERING AND PROJECTION ------------------------------------------------------------------------------- people.Where(p => p.Age >= 18) people.Select(p => p.Name) people.Select((p, index) => $"{index}: {p.Name}") people.SelectMany(p => p.PhoneNumbers) // flattens nested collections people.OfType() // filters by runtime type ------------------------------------------------------------------------------- ORDERING ------------------------------------------------------------------------------- people.OrderBy(p => p.LastName) people.OrderByDescending(p => p.Age) people.OrderBy(p => p.LastName).ThenBy(p => p.FirstName) people.Reverse() ------------------------------------------------------------------------------- GROUPING AND AGGREGATION ------------------------------------------------------------------------------- people.GroupBy(p => p.Department) people.Aggregate((a, b) => a.Age > b.Age ? a : b) // fold people.Aggregate(0, (sum, p) => sum + p.Age) // seeded fold people.Count() people.Count(p => p.Age >= 18) people.Sum(p => p.Salary) people.Average(p => p.Age) people.Min(p => p.Age) people.Max(p => p.Age) var byDept = people .GroupBy(p => p.Department) .Select(g => new { Department = g.Key, Count = g.Count() }); ------------------------------------------------------------------------------- SET-STYLE AND SLICE OPERATIONS ------------------------------------------------------------------------------- people.Distinct() people.DistinctBy(p => p.Email) // .NET 6+ people.Skip(10) people.Take(5) people.SkipWhile(p => p.Age < 18) people.TakeWhile(p => p.Age < 65) people.Chunk(3) // .NET 6+, splits into arrays of 3 ------------------------------------------------------------------------------- JOIN AND ZIP ------------------------------------------------------------------------------- var query = from order in orders join customer in customers on order.CustomerId equals customer.Id select new { order.Id, customer.Name }; var joined = orders.Join( customers, order => order.CustomerId, customer => customer.Id, (order, customer) => new { order.Id, customer.Name }); var zipped = list1.Zip(list2, (a, b) => a + b); ------------------------------------------------------------------------------- CONVERSION ------------------------------------------------------------------------------- people.ToList() people.ToArray() people.ToDictionary(p => p.Id) people.ToDictionary(p => p.Id, p => p.Name) people.ToLookup(p => p.Department) // like ToDictionary but allows // duplicate keys, one-to-many people.ToHashSet() ------------------------------------------------------------------------------- ELEMENT AND BOOLEAN OPERATIONS ------------------------------------------------------------------------------- people.Any() people.Any(p => p.Age >= 18) people.All(p => p.Age >= 18) people.Contains(somePerson) people.First() // throws if empty people.FirstOrDefault() // null/default if empty people.FirstOrDefault(p => p.Age >= 18) people.Last() people.LastOrDefault() people.Single() // throws unless exactly one element people.SingleOrDefault() people.ElementAt(2) people.ElementAtOrDefault(2) ------------------------------------------------------------------------------- DEFERRED EXECUTION ------------------------------------------------------------------------------- LINQ queries built with Where/Select/etc. are lazy -- they do not run until enumerated (via foreach, ToList(), Count(), etc). This means the same query can produce different results if the underlying data changes between definition and enumeration. var query = numbers.Where(n => n > threshold); // not run yet threshold = 100; foreach (var n in query) { ... } // runs now, uses 100 ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var topEarners = employees .Where(e => e.Salary > 100000) .OrderByDescending(e => e.Salary) .Select(e => new { e.Name, e.Salary }) .Take(5) .ToList(); ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Where / Select / SelectMany O(n), lazy, one pass on enumeration OrderBy / OrderByDescending O(n log n) GroupBy O(n) average Distinct O(n) average, uses a hash set internally Count() on IEnumerable O(n) unless the source has a fast Count (arrays and List use O(1) directly) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Calling .Count() > 0 instead of .Any() to check for emptiness. Any() stops at the first element; Count() may enumerate the entire sequence depending on the source type. Enumerating the same deferred query multiple times when the source is expensive (a database query, a web call), re-running the whole operation each time. Materialize once with .ToList() if you need to reuse the result. Using First() when the sequence might be empty, causing an unhandled InvalidOperationException in production. Prefer FirstOrDefault() and check for null/default explicitly. Chaining so many LINQ operators that a simple loop would be clearer and faster to both read and execute -- LINQ readability is a trade-off, not a free win, especially inside hot paths. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain deferred (lazy) execution vs immediate execution, and name which LINQ methods force immediate evaluation (ToList, ToArray, ToDictionary, Count, Sum, First, etc. all force it). Be ready to translate a LINQ chain into the equivalent explicit for/foreach loop and vice versa -- interviewers sometimes ask this to confirm you understand what's happening under the hood, not just the syntax. ================================================================================ BINARY SEARCH ================================================================================ DESCRIPTION A divide-and-conquer search over a sorted range that eliminates half the remaining candidates on each step. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Classic binary search: static int BinarySearch(int[] nums, int target) { int left = 0, right = nums.Length - 1; while (left <= right) { int mid = left + (right - left) / 2; // avoids overflow if (nums[mid] == target) return mid; if (nums[mid] < target) left = mid + 1; else right = mid - 1; } return -1; } Lower bound / leftmost insertion point (first index where nums[i] >= target): static int LowerBound(int[] nums, int target) { int left = 0, right = nums.Length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] < target) left = mid + 1; else right = mid; } return left; } Binary search on the answer (common pattern for optimization problems, e.g. "minimum capacity to ship packages within D days"): static int MinimumCapacity(int lo, int hi, Func canFinish) { while (lo < hi) { int mid = lo + (hi - lo) / 2; if (canFinish(mid)) hi = mid; else lo = mid + 1; } return lo; } Built-in: Array.BinarySearch(sortedArray, target); list.BinarySearch(target); ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Binary search O(log n) Requires the input to already be sorted; sorting first costs O(n log n) if it is not. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Writing mid = (left + right) / 2, which can overflow for very large indices. Use left + (right - left) / 2 instead. Getting the loop boundary condition wrong (<= vs <) and either missing the last element or looping forever. Decide up front whether "right" is inclusive or exclusive and stay consistent. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- "Binary search on the answer" is a distinct pattern from "binary search on an array" -- recognizing when a problem's answer space is monotonic (if X works, does X+1 also work?) is the key insight for a whole category of optimization problems. ================================================================================ TREE DFS ================================================================================ DESCRIPTION Depth-first traversal of a tree, going as deep as possible down one branch before backtracking. Three common orders: preorder, inorder, postorder. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- public class TreeNode { public int val; public TreeNode? left; public TreeNode? right; public TreeNode(int val = 0, TreeNode? left = null, TreeNode? right = null) { this.val = val; this.left = left; this.right = right; } } Recursive preorder (root, left, right): static void Preorder(TreeNode? node, List result) { if (node == null) return; result.Add(node.val); Preorder(node.left, result); Preorder(node.right, result); } Recursive inorder (left, root, right) -- yields sorted order for a BST: static void Inorder(TreeNode? node, List result) { if (node == null) return; Inorder(node.left, result); result.Add(node.val); Inorder(node.right, result); } Recursive postorder (left, right, root): static void Postorder(TreeNode? node, List result) { if (node == null) return; Postorder(node.left, result); Postorder(node.right, result); result.Add(node.val); } Iterative preorder using an explicit stack: static List PreorderIterative(TreeNode? root) { var result = new List(); if (root == null) return result; var stack = new Stack(); stack.Push(root); while (stack.Count > 0) { var node = stack.Pop(); result.Add(node.val); if (node.right != null) stack.Push(node.right); if (node.left != null) stack.Push(node.left); } return result; } Max depth: static int MaxDepth(TreeNode? node) => node == null ? 0 : 1 + Math.Max(MaxDepth(node.left), MaxDepth(node.right)); ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Any full traversal O(n), visits every node once Space (recursive) O(h), h = height of the tree, due to call stack; O(n) worst case for a completely unbalanced tree ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting the null base case, causing a NullReferenceException the moment recursion hits a leaf's child. Confusing preorder/inorder/postorder under interview pressure -- memorize them by remembering where "root" falls in the name relative to left and right. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Inorder traversal of a valid BST always yields values in sorted order -- a frequently used fact for "validate BST" and "kth smallest element in a BST" problems. ================================================================================ TREE BFS ================================================================================ DESCRIPTION Breadth-first (level-order) traversal of a tree, visiting all nodes at depth d before any node at depth d+1. Implemented with a Queue. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Level order traversal, grouped by level: static List> LevelOrder(TreeNode? root) { var result = new List>(); if (root == null) return result; var queue = new Queue(); queue.Enqueue(root); while (queue.Count > 0) { int levelSize = queue.Count; var level = new List(); for (int i = 0; i < levelSize; i++) { var node = queue.Dequeue(); level.Add(node.val); if (node.left != null) queue.Enqueue(node.left); if (node.right != null) queue.Enqueue(node.right); } result.Add(level); } return result; } Right side view (last node visible from the right at each level): static List RightSideView(TreeNode? root) { var result = new List(); if (root == null) return result; var queue = new Queue(); queue.Enqueue(root); while (queue.Count > 0) { int levelSize = queue.Count; for (int i = 0; i < levelSize; i++) { var node = queue.Dequeue(); if (i == levelSize - 1) result.Add(node.val); if (node.left != null) queue.Enqueue(node.left); if (node.right != null) queue.Enqueue(node.right); } } return result; } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Full traversal O(n) Space O(w), w = maximum width of the tree, O(n) worst case ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting to capture queue.Count into a fixed levelSize variable before the inner loop, which breaks level-by-level grouping because the queue's count keeps changing as children are enqueued mid-loop. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Any problem phrased "level by level" or "shortest path in an unweighted tree/graph" is a strong signal to reach for BFS over DFS. ================================================================================ BACKTRACKING ================================================================================ DESCRIPTION A systematic way to explore all candidate solutions by building them incrementally and abandoning ("backtracking" from) a partial candidate as soon as it cannot possibly lead to a valid solution. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Subsets (power set): static List> Subsets(int[] nums) { var result = new List>(); var current = new List(); Backtrack(0); return result; void Backtrack(int start) { result.Add(new List(current)); for (int i = start; i < nums.Length; i++) { current.Add(nums[i]); Backtrack(i + 1); current.RemoveAt(current.Count - 1); // undo } } } Permutations: static List> Permute(int[] nums) { var result = new List>(); var current = new List(); var used = new bool[nums.Length]; Backtrack(); return result; void Backtrack() { if (current.Count == nums.Length) { result.Add(new List(current)); return; } for (int i = 0; i < nums.Length; i++) { if (used[i]) continue; used[i] = true; current.Add(nums[i]); Backtrack(); current.RemoveAt(current.Count - 1); // undo used[i] = false; // undo } } } Combination Sum (reuse elements allowed): static List> CombinationSum(int[] candidates, int target) { var result = new List>(); var current = new List(); Backtrack(0, target); return result; void Backtrack(int start, int remaining) { if (remaining == 0) { result.Add(new List(current)); return; } if (remaining < 0) return; for (int i = start; i < candidates.Length; i++) { current.Add(candidates[i]); Backtrack(i, remaining - candidates[i]); // i, not i + 1: reuse current.RemoveAt(current.Count - 1); } } } N-Queens style board backtracking follows the same shape: place a piece, recurse, check a validity function, and undo the placement on the way back up. ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Highly problem-dependent, usually exponential in the worst case (e.g. O(2^n) for subsets, O(n!) for permutations), because backtracking explores a search tree. Pruning (early return on an invalid partial state) reduces the effective branching factor in practice without changing the theoretical worst case. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting the "undo" step after the recursive call (removing the last added element, un-marking a used flag), which leaves stale state polluting later branches of the search tree. Adding "current" itself to "result" instead of a copy, so every stored result is later mutated by continued backtracking (see the same mistake noted in the LIST section). ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Backtracking problems almost always follow the same skeleton: choose, explore (recurse), un-choose. Internalizing that skeleton makes it much easier to adapt to subsets, permutations, combination sum, N-Queens, word search, and Sudoku solvers, which all share the same shape with different validity/termination conditions. ================================================================================ SLIDING WINDOW ================================================================================ DESCRIPTION Maintains a contiguous subrange (the "window") over an array or string and expands/shrinks it incrementally, avoiding the need to recompute from scratch for every possible subrange. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Fixed-size window, max sum of any k consecutive elements: static int MaxSumSubarray(int[] nums, int k) { int windowSum = 0; for (int i = 0; i < k; i++) windowSum += nums[i]; int maxSum = windowSum; for (int i = k; i < nums.Length; i++) { windowSum += nums[i] - nums[i - k]; // slide by one maxSum = Math.Max(maxSum, windowSum); } return maxSum; } Variable-size window, longest substring without repeating characters: static int LengthOfLongestSubstring(string s) { var lastSeen = new Dictionary(); int left = 0, longest = 0; for (int right = 0; right < s.Length; right++) { char c = s[right]; if (lastSeen.TryGetValue(c, out int prevIndex) && prevIndex >= left) { left = prevIndex + 1; // shrink window past the duplicate } lastSeen[c] = right; longest = Math.Max(longest, right - left + 1); } return longest; } Minimum window substring (shrink while still valid, then try to shrink further): static string MinWindow(string s, string t) { var need = new Dictionary(); foreach (var c in t) need[c] = need.GetValueOrDefault(c) + 1; var window = new Dictionary(); int have = 0, needCount = need.Count; int left = 0, bestLen = int.MaxValue, bestStart = 0; for (int right = 0; right < s.Length; right++) { char c = s[right]; window[c] = window.GetValueOrDefault(c) + 1; if (need.ContainsKey(c) && window[c] == need[c]) have++; while (have == needCount) { if (right - left + 1 < bestLen) { bestLen = right - left + 1; bestStart = left; } char leftChar = s[left]; window[leftChar]--; if (need.ContainsKey(leftChar) && window[leftChar] < need[leftChar]) have--; left++; } } return bestLen == int.MaxValue ? "" : s.Substring(bestStart, bestLen); } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Sliding window O(n), each index enters and leaves the window at most once, even though it looks like nested loops ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Recomputing the window sum/state from scratch on every slide instead of incrementally adding/removing the entering/leaving element, turning an O(n) algorithm into O(n*k) or O(n^2). Off-by-one errors on the window boundaries, especially around whether "right" is inclusive. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- A strong signal for sliding window: the problem mentions a "contiguous subarray/substring" and asks for a max/min/count subject to some constraint. Recognizing fixed-size vs variable-size window upfront saves a lot of false starts. ================================================================================ TWO POINTERS ================================================================================ DESCRIPTION Uses two indices moving through a sequence (from both ends inward, or both moving forward at different speeds) to avoid nested loops. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Two Sum on a sorted array (opposite ends moving inward): static int[] TwoSumSorted(int[] nums, int target) { int left = 0, right = nums.Length - 1; while (left < right) { int sum = nums[left] + nums[right]; if (sum == target) return new[] { left, right }; if (sum < target) left++; else right--; } return Array.Empty(); } Remove duplicates from a sorted array in place (slow/fast pointers): static int RemoveDuplicates(int[] nums) { if (nums.Length == 0) return 0; int slow = 0; for (int fast = 1; fast < nums.Length; fast++) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; } } return slow + 1; } Container With Most Water: static int MaxArea(int[] height) { int left = 0, right = height.Length - 1, best = 0; while (left < right) { int area = Math.Min(height[left], height[right]) * (right - left); best = Math.Max(best, area); if (height[left] < height[right]) left++; else right--; } return best; } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Two pointers O(n), each pointer moves forward monotonically, so the total number of steps across both pointers is bounded by n ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Applying the "opposite ends" two-pointer pattern on unsorted data when it requires sortedness to work correctly (e.g. Two Sum on an unsorted array needs a dictionary approach instead, or sorting first, which changes the original indices). Off-by-one errors in the loop condition (< vs <=), especially in "remove duplicates in place" style problems. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Two pointers and sliding window are closely related; the difference is that two pointers often work on sorted arrays comparing values at both ends, while sliding window tracks a contiguous range and its running state. ================================================================================ UNION FIND ================================================================================ DESCRIPTION Also called Disjoint Set Union (DSU). Tracks a partition of elements into disjoint sets, supporting near-constant-time "are these connected" queries and merges. The workhorse for connectivity and cycle-detection problems on undirected graphs. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public class UnionFind { private readonly int[] _parent; private readonly 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]); // path compression } return _parent[x]; } public bool Union(int a, int b) { int rootA = Find(a); int rootB = Find(b); if (rootA == rootB) return false; // already connected if (_rank[rootA] < _rank[rootB]) { (rootA, rootB) = (rootB, rootA); } _parent[rootB] = rootA; if (_rank[rootA] == _rank[rootB]) _rank[rootA]++; return true; } } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- uf.Find(x) // finds the representative/root of x's set uf.Union(a, b) // merges the sets containing a and b ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Detect a cycle while building an undirected graph edge by edge: var uf = new UnionFind(n); foreach (var (a, b) in edges) { if (!uf.Union(a, b)) { Console.WriteLine("Cycle detected!"); break; } } Count connected components: var uf = new UnionFind(n); foreach (var (a, b) in edges) uf.Union(a, b); int components = Enumerable.Range(0, n) .Select(uf.Find) .Distinct() .Count(); ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Find / Union (with path compression + union by rank) effectively O(alpha(n)), where alpha is the inverse Ackermann function -- so close to O(1) that it is treated as constant time in practice for any realistic n. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Implementing Find without path compression, which degrades to O(n) per call in the worst case (a long chain), instead of near O(1). Forgetting Union by rank/size, which can also lead to long chains and slower Find calls, though path compression alone still keeps it fast in practice. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Union-Find is the standard tool whenever a problem talks about "groups," "connected components," "provinces," or "redundant connection" (cycle detection) on an undirected graph -- often faster and simpler to write correctly than a full DFS/BFS-based solution for these specific questions. ================================================================================ TRIE ================================================================================ DESCRIPTION A prefix tree, used for fast string prefix lookups: autocomplete, spell checking, and word search style problems. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public class TrieNode { public Dictionary Children { get; } = new(); public bool IsEndOfWord { get; set; } } public class Trie { private readonly TrieNode _root = new(); public void Insert(string word) { var node = _root; foreach (char c in word) { if (!node.Children.TryGetValue(c, out var next)) { next = new TrieNode(); node.Children[c] = next; } node = next; } node.IsEndOfWord = true; } public bool Search(string word) { var node = FindNode(word); return node is { IsEndOfWord: true }; } public bool StartsWith(string prefix) => FindNode(prefix) != null; private TrieNode? FindNode(string s) { var node = _root; foreach (char c in s) { if (!node.Children.TryGetValue(c, out var next)) return null; node = next; } return node; } } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- trie.Insert("apple") trie.Search("apple") // exact word match trie.StartsWith("app") // prefix match ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Insert / Search / StartsWith O(L), L = length of the word/prefix, independent of how many words are already stored Space O(total characters across all inserted words), with shared prefixes stored once ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using a fixed-size array of 26 children (for lowercase-only alphabets) when the actual character set is broader (unicode, digits, punctuation) -- a Dictionary is more flexible at a small performance cost. Forgetting to mark IsEndOfWord, which makes Search return true for any inserted prefix, not just complete words. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Recognize Trie problems by the words "prefix," "autocomplete," or "word search on a board" -- Word Search II (searching a 2D board for a list of words) is a classic trie + backtracking combination problem. ================================================================================ HEAP ================================================================================ DESCRIPTION A heap is the underlying data structure behind PriorityQueue -- a complete binary tree stored in an array where each parent is smaller (min-heap) or larger (max-heap) than its children. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- In practice, always use the built-in PriorityQueue (see the PRIORITY QUEUE section) rather than hand-rolling a heap. Hand-rolled heaps mostly show up when an interviewer explicitly asks you to implement one from scratch. A minimal manual min-heap over an array, for reference: public class MinHeap { private readonly List _data = new(); public void Push(int value) { _data.Add(value); int i = _data.Count - 1; while (i > 0) { int parent = (i - 1) / 2; if (_data[parent] <= _data[i]) break; (_data[parent], _data[i]) = (_data[i], _data[parent]); i = parent; } } public int Pop() { int top = _data[0]; _data[0] = _data[^1]; _data.RemoveAt(_data.Count - 1); int i = 0; while (true) { int left = 2 * i + 1, right = 2 * i + 2, smallest = i; if (left < _data.Count && _data[left] < _data[smallest]) smallest = left; if (right < _data.Count && _data[right] < _data[smallest]) smallest = right; if (smallest == i) break; (_data[i], _data[smallest]) = (_data[smallest], _data[i]); i = smallest; } return top; } } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Push / Pop O(log n) Peek O(1) Build a heap from n elements all at once O(n), not O(n log n) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Reimplementing a heap by hand in an interview when PriorityQueue is available and appropriate -- unless explicitly asked to implement one, use the built-in type. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- See the PRIORITY QUEUE section for the "top-k" and "Dijkstra" heap patterns that come up constantly in interviews. ================================================================================ RECURSION AND MEMOIZATION ================================================================================ DESCRIPTION Recursion solves a problem by breaking it into smaller instances of the same problem. Memoization caches results of expensive recursive calls so identical subproblems are computed only once. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Naive recursive Fibonacci (exponential time, do not use as-is): static long Fib(int n) => n <= 1 ? n : Fib(n - 1) + Fib(n - 2); Memoized Fibonacci (top-down dynamic programming): static long Fib(int n, Dictionary? memo = null) { memo ??= new Dictionary(); if (n <= 1) return n; if (memo.TryGetValue(n, out long cached)) return cached; long result = Fib(n - 1, memo) + Fib(n - 2, memo); memo[n] = result; return result; } Same idea with a simple array cache when subproblems are indexed by a small integer range: static long[] _cache = new long[100]; static bool[] _computed = new bool[100]; static long FibArray(int n) { if (n <= 1) return n; if (_computed[n]) return _cache[n]; long result = FibArray(n - 1) + FibArray(n - 2); _cache[n] = result; _computed[n] = true; return result; } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- Naive recursive Fibonacci O(2^n), recomputes the same subproblems repeatedly Memoized Fibonacci O(n), each subproblem computed once ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting a base case, causing a StackOverflowException instead of a graceful failure -- unlike most exceptions, this one generally cannot be caught in a try/catch and crashes the process. Adding memoization to a problem whose subproblems are never actually repeated, adding overhead without benefit. Memoization pays off specifically when the same subproblem is reached via multiple recursive paths (overlapping subproblems), which is one of the two defining properties of dynamic programming. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to state the two properties that make memoization worth applying: overlapping subproblems, and optimal substructure (the optimal solution to the whole problem can be built from optimal solutions to its subproblems). ================================================================================ DYNAMIC PROGRAMMING ================================================================================ DESCRIPTION Solves problems by breaking them into overlapping subproblems and storing solutions to avoid recomputation, either top-down (recursion plus memoization) or bottom-up (iterative table filling). ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Bottom-up Fibonacci, O(1) space: static long FibBottomUp(int n) { if (n <= 1) return n; long prev2 = 0, prev1 = 1; for (int i = 2; i <= n; i++) { long current = prev1 + prev2; prev2 = prev1; prev1 = current; } return prev1; } Climbing Stairs (1D DP, ways to reach step n taking 1 or 2 steps at a time): static int ClimbStairs(int n) { if (n <= 2) return n; int[] dp = new int[n + 1]; dp[1] = 1; dp[2] = 2; for (int i = 3; i <= n; i++) dp[i] = dp[i - 1] + dp[i - 2]; return dp[n]; } 0/1 Knapsack (2D DP): static int Knapsack(int[] weights, int[] values, int capacity) { int n = weights.Length; int[,] dp = new int[n + 1, capacity + 1]; for (int i = 1; i <= n; i++) { for (int w = 0; w <= capacity; w++) { dp[i, w] = dp[i - 1, w]; // don't take item i if (weights[i - 1] <= w) { dp[i, w] = Math.Max( dp[i, w], dp[i - 1, w - weights[i - 1]] + values[i - 1]); // take it } } } return dp[n, capacity]; } Longest Common Subsequence (2D DP over two strings): static int Lcs(string a, string b) { int[,] dp = new int[a.Length + 1, b.Length + 1]; for (int i = 1; i <= a.Length; i++) { for (int j = 1; j <= b.Length; j++) { dp[i, j] = a[i - 1] == b[j - 1] ? dp[i - 1, j - 1] + 1 : Math.Max(dp[i - 1, j], dp[i, j - 1]); } } return dp[a.Length, b.Length]; } Coin Change (minimum coins to make an amount): static int CoinChange(int[] coins, int amount) { int[] dp = new int[amount + 1]; Array.Fill(dp, amount + 1); dp[0] = 0; for (int i = 1; i <= amount; i++) { foreach (var coin in coins) { if (coin <= i) { dp[i] = Math.Min(dp[i], dp[i - coin] + 1); } } } return dp[amount] > amount ? -1 : dp[amount]; } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- 1D DP (Fibonacci, climbing stairs, coin change) O(n) or O(n * k) 2D DP (knapsack, LCS, edit distance) O(n * m) Space can often be reduced from O(n*m) to O(min(n, m)) by only keeping the current and previous row when the recurrence only looks one row back. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Not identifying the recurrence relation clearly before coding -- write dp[i] (or dp[i, j]) as a plain-English sentence first ("the minimum coins to make amount i"), then translate it into code. Off-by-one errors on the dp array size (needing n+1 rather than n to include a "zero items" or "empty string" base case). Using recursion without memoization on a problem with overlapping subproblems, silently degrading to exponential time even though the logic is "correct." ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- A reliable approach for any DP interview question: define the subproblem in words, write the recurrence, decide the base cases, decide iteration order (which direction fills the table correctly), then consider whether space can be compressed. Interviewers often care more about this reasoning process than the final code. ================================================================================ BIT MANIPULATION ================================================================================ DESCRIPTION Manipulating individual bits of an integer directly, used for compact state representation, fast arithmetic tricks, and a recurring category of interview questions. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Check if a bit is set: bool IsBitSet(int n, int i) => (n & (1 << i)) != 0; Set / clear / toggle a bit: int SetBit(int n, int i) => n | (1 << i); int ClearBit(int n, int i) => n & ~(1 << i); int ToggleBit(int n, int i) => n ^ (1 << i); Count set bits (Brian Kernighan's algorithm): static int CountSetBits(int n) { int count = 0; while (n != 0) { n &= (n - 1); // clears the lowest set bit count++; } return count; } // Or, built-in (System.Numerics.BitOperations, .NET Core 3+): int count2 = System.Numerics.BitOperations.PopCount((uint)n); Single Number (every element appears twice except one, found via XOR): static int SingleNumber(int[] nums) { int result = 0; foreach (var n in nums) result ^= n; return result; } Check if a number is a power of two: bool IsPowerOfTwo(int n) => n > 0 && (n & (n - 1)) == 0; Swap two variables without a temp variable (rarely useful in practice, but a classic interview trick): a ^= b; b ^= a; a ^= b; ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- All single-bit operations O(1) Counting set bits (Kernighan's) O(k), k = number of set bits, faster than O(32) naive bit-by-bit scan when the number is sparse ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using ^ (XOR) when meaning ** (there is no exponent operator in C#; use Math.Pow) -- a common typo carried over from other languages/pseudocode. Forgetting that C#'s >> on a signed integer is an arithmetic shift (sign-extends), which can produce a negative result for negative inputs; use >>> (unsigned right shift, C# 11+) when a logical shift is intended. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- n & (n - 1) to drop the lowest set bit, and n & -n to isolate the lowest set bit, are the two bit tricks worth having completely memorized -- they show up across dozens of different-looking problems (Single Number, Counting Bits, Power of Two, subsets via bitmask). ================================================================================ GRAPHS ================================================================================ DESCRIPTION Graphs model pairwise relationships between nodes. In interview contexts they are almost always represented as an adjacency list. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- Adjacency list, unweighted: var graph = new Dictionary>(); void AddEdge(int a, int b) { if (!graph.ContainsKey(a)) graph[a] = new List(); if (!graph.ContainsKey(b)) graph[b] = new List(); graph[a].Add(b); graph[b].Add(a); // omit this line for a directed graph } Adjacency list, weighted: var graph = new Dictionary>(); Adjacency matrix (fine for dense graphs or small fixed n): int[,] adjMatrix = new int[n, n]; ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- See TREE DFS / TREE BFS / QUEUE / STACK sections -- the same traversal code applies directly to general graphs, with the addition of a visited set to avoid infinite loops in the presence of cycles. ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- General graph DFS with a visited set (recursive): static void Dfs(Dictionary> graph, int node, HashSet visited) { if (!visited.Add(node)) return; Console.WriteLine(node); foreach (var neighbor in graph.GetValueOrDefault(node, new List())) { Dfs(graph, neighbor, visited); } } Topological sort (Kahn's algorithm, BFS-based, for DAGs): static List TopologicalSort(int n, List<(int from, int to)> edges) { var graph = new Dictionary>(); var inDegree = new int[n]; foreach (var (from, to) in edges) { if (!graph.ContainsKey(from)) graph[from] = new List(); graph[from].Add(to); inDegree[to]++; } var queue = new Queue(); for (int i = 0; i < n; i++) if (inDegree[i] == 0) queue.Enqueue(i); var order = new List(); while (queue.Count > 0) { int node = queue.Dequeue(); order.Add(node); foreach (var next in graph.GetValueOrDefault(node, new List())) { if (--inDegree[next] == 0) queue.Enqueue(next); } } return order.Count == n ? order : new List(); // empty if a cycle exists } Number of islands (grid-based DFS/BFS, extremely common interview question): static int NumIslands(char[][] grid) { int rows = grid.Length, cols = grid[0].Length, count = 0; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { if (grid[r][c] == '1') { count++; Sink(r, c); } } } return count; void Sink(int r, int c) { if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] != '1') return; grid[r][c] = '0'; Sink(r + 1, c); Sink(r - 1, c); Sink(r, c + 1); Sink(r, c - 1); } } ------------------------------------------------------------------------------- TIME COMPLEXITY ------------------------------------------------------------------------------- DFS / BFS (adjacency list) O(V + E), V = vertices, E = edges DFS / BFS (adjacency matrix) O(V^2) Topological sort (Kahn's) O(V + E) Dijkstra's (see PRIORITY QUEUE) O((V + E) log V) with a binary heap ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting a visited set on a graph with cycles, causing infinite recursion/looping -- trees never need this because they have no cycles, which trips people up moving from tree problems to graph problems. Using recursive DFS on a graph that might be very deep/large, risking a StackOverflowException; switch to an iterative DFS with an explicit stack for graphs where depth is unbounded or unknown. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Grid problems (number of islands, rotting oranges, surrounded regions) are graph problems in disguise -- each cell is a node, adjacency is up/down/left/right, and the same DFS/BFS toolbox applies directly. ================================================================================ ASYNC ================================================================================ DESCRIPTION C#'s async/await lets I/O-bound (and some CPU-bound) work run without blocking a thread while waiting, built on top of the Task and Task types. ------------------------------------------------------------------------------- CREATE ------------------------------------------------------------------------------- public async Task FetchDataAsync(string url) { using var client = new HttpClient(); var response = await client.GetStringAsync(url); return response; } public async Task DoWorkAsync() { await Task.Delay(1000); Console.WriteLine("Done"); } ValueTask for high-frequency calls that often complete synchronously, avoiding a Task allocation on the hot path: public async ValueTask GetCachedOrComputeAsync(string key) { if (_cache.TryGetValue(key, out int value)) return value; return await ComputeExpensiveAsync(key); } ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- await someTask Task.Run(() => SomeCpuBoundWork()) Task.WhenAll(task1, task2, task3) Task.WhenAny(task1, task2) Task.Delay(milliseconds) Task.FromResult(value) Task.CompletedTask task.Result // blocks, avoid in async code (deadlock risk) task.Wait() // blocks, avoid in async code task.IsCompleted / task.IsFaulted / task.IsCanceled ------------------------------------------------------------------------------- CANCELLATION ------------------------------------------------------------------------------- public async Task DoWorkAsync(CancellationToken token) { for (int i = 0; i < 1000; i++) { token.ThrowIfCancellationRequested(); await Task.Delay(10, token); } } using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); try { await DoWorkAsync(cts.Token); } catch (OperationCanceledException) { Console.WriteLine("Cancelled"); } ------------------------------------------------------------------------------- SYNCHRONIZATION PRIMITIVES ------------------------------------------------------------------------------- lock (_syncRoot) // classic monitor-based lock, { // synchronous code only _counter++; } private readonly SemaphoreSlim _semaphore = new(1, 1); async Task DoAsync() { await _semaphore.WaitAsync(); try { // critical section, async-safe unlike lock } finally { _semaphore.Release(); } } ConcurrentDictionary concurrentMap = new(); concurrentMap.AddOrUpdate("key", 1, (k, old) => old + 1); concurrentMap.TryGetValue("key", out int value); ------------------------------------------------------------------------------- PARALLEL AND CHANNELS ------------------------------------------------------------------------------- Parallel.ForEach(items, item => { ProcessItem(item); // CPU-bound work spread across threads }); var channel = Channel.CreateUnbounded(); async Task ProducerAsync() { for (int i = 0; i < 10; i++) { await channel.Writer.WriteAsync(i); } channel.Writer.Complete(); } async Task ConsumerAsync() { await foreach (var item in channel.Reader.ReadAllAsync()) { Console.WriteLine(item); } } ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- Running independent async calls concurrently instead of sequentially: var task1 = FetchDataAsync(url1); var task2 = FetchDataAsync(url2); var results = await Task.WhenAll(task1, task2); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Calling task.Result or task.Wait() from synchronous code that is itself called from an async context with a captured SynchronizationContext (classic ASP.NET, WPF, WinForms) -- a well-known deadlock trap. Use await consistently, or ConfigureAwait(false) in library code that does not need to resume on the original context. "async void" methods (other than event handlers) -- exceptions thrown inside them cannot be awaited/caught by the caller and will crash the process instead. Always return Task, not void, unless it's a UI event handler. Forgetting to pass a CancellationToken through an entire async call chain, making a "cancellable" operation not actually cancellable partway through. Using lock around awaited code -- lock cannot be held across an await, and the compiler will refuse to build it. Use SemaphoreSlim instead for async-safe mutual exclusion. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain why async/await frees up threads while waiting on I/O (the thread is returned to the pool during the await, not blocked), versus Task.Run, which explicitly schedules CPU-bound work onto a thread-pool thread. Know the classic ASP.NET classic (not Core) deadlock scenario caused by blocking on async code with .Result inside a request that has a captured synchronization context, and why ASP.NET Core mostly avoids this problem (no SynchronizationContext by default). ================================================================================ ASPNET CORE ================================================================================ DESCRIPTION ASP.NET Core is the cross-platform web framework for building APIs, MVC apps, and real-time services on .NET. ------------------------------------------------------------------------------- PROGRAM.CS (MINIMAL APIS) ------------------------------------------------------------------------------- var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); builder.Services.AddScoped(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapGet("/health", () => Results.Ok("Healthy")); app.MapGet("/orders/{id:int}", async (int id, IOrderService svc) => { var order = await svc.GetByIdAsync(id); return order is null ? Results.NotFound() : Results.Ok(order); }); app.Run(); ------------------------------------------------------------------------------- CONTROLLERS (MVC-STYLE) ------------------------------------------------------------------------------- [ApiController] [Route("api/[controller]")] public class OrdersController : ControllerBase { private readonly IOrderService _orders; public OrdersController(IOrderService orders) => _orders = orders; [HttpGet("{id:int}")] public async Task GetById(int id) { var order = await _orders.GetByIdAsync(id); return order is null ? NotFound() : Ok(order); } [HttpPost] public async Task Create([FromBody] CreateOrderRequest request) { if (!ModelState.IsValid) return BadRequest(ModelState); var created = await _orders.CreateAsync(request); return CreatedAtAction(nameof(GetById), new { id = created.Id }, created); } } ------------------------------------------------------------------------------- MIDDLEWARE ------------------------------------------------------------------------------- app.Use(async (context, next) => { var stopwatch = Stopwatch.StartNew(); await next(); Console.WriteLine($"{context.Request.Path} took {stopwatch.ElapsedMilliseconds}ms"); }); Custom middleware class: public class RequestTimingMiddleware { private readonly RequestDelegate _next; public RequestTimingMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext context) { var sw = Stopwatch.StartNew(); await _next(context); Console.WriteLine($"{context.Request.Path}: {sw.ElapsedMilliseconds}ms"); } } app.UseMiddleware(); ------------------------------------------------------------------------------- DEPENDENCY INJECTION ------------------------------------------------------------------------------- builder.Services.AddSingleton(); // one instance, app lifetime builder.Services.AddScoped(); // one instance per request builder.Services.AddTransient(); // new instance every time Constructor injection is the standard pattern: public class OrderService : IOrderService { private readonly AppDbContext _db; private readonly ILogger _logger; public OrderService(AppDbContext db, ILogger logger) { _db = db; _logger = logger; } } ------------------------------------------------------------------------------- AUTHENTICATION AND JWT ------------------------------------------------------------------------------- builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = config["Jwt:Issuer"], ValidAudience = config["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(config["Jwt:Key"]!)) }; }); [Authorize] [HttpGet("me")] public IActionResult GetCurrentUser() => Ok(new { UserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value }); [Authorize(Roles = "Admin")] [HttpDelete("{id}")] public IActionResult Delete(int id) => NoContent(); ------------------------------------------------------------------------------- CONFIGURATION AND OPTIONS PATTERN ------------------------------------------------------------------------------- public class SmtpSettings { public string Host { get; set; } = ""; public int Port { get; set; } } builder.Services.Configure(builder.Configuration.GetSection("Smtp")); public class EmailSender { private readonly SmtpSettings _settings; public EmailSender(IOptions options) => _settings = options.Value; } ------------------------------------------------------------------------------- LOGGING ------------------------------------------------------------------------------- public class OrderService { private readonly ILogger _logger; public OrderService(ILogger logger) => _logger = logger; public void Process(int orderId) { _logger.LogInformation("Processing order {OrderId}", orderId); _logger.LogWarning("Order {OrderId} is missing a shipping address", orderId); _logger.LogError(exception, "Failed to process order {OrderId}", orderId); } } ------------------------------------------------------------------------------- MODEL BINDING AND VALIDATION ------------------------------------------------------------------------------- public class CreateOrderRequest { [Required] public string CustomerName { get; set; } = ""; [Range(1, int.MaxValue)] public int Quantity { get; set; } [EmailAddress] public string Email { get; set; } = ""; } [HttpGet] public IActionResult Search([FromQuery] string term, [FromHeader] string? apiKey) => Ok(); ------------------------------------------------------------------------------- SIGNALR (REAL-TIME) ------------------------------------------------------------------------------- public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } } builder.Services.AddSignalR(); app.MapHub("/chatHub"); ------------------------------------------------------------------------------- BACKGROUND SERVICES ------------------------------------------------------------------------------- public class CleanupWorker : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await DoCleanupAsync(); await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken); } } } builder.Services.AddHostedService(); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Registering a DbContext (which is meant to be Scoped) as Singleton, causing thread-safety issues since DbContext is not designed to be shared across concurrent requests. Injecting a Scoped service into a Singleton service directly, which throws at startup or resolves a captured, stale instance -- resolve Scoped dependencies from an IServiceScopeFactory inside the Singleton instead. Forgetting [ApiController] or explicit [FromBody]/[FromQuery] attributes and being surprised when model binding does not pick up values from where you expected. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain the three DI lifetimes (Singleton, Scoped, Transient) with a concrete example of when each is appropriate, and the specific danger of "captive dependencies" (a longer-lived service holding a reference to a shorter-lived one). ================================================================================ ENTITY FRAMEWORK CORE ================================================================================ DESCRIPTION EF Core is the standard object-relational mapper (ORM) for .NET, mapping C# classes to database tables and LINQ queries to SQL. ------------------------------------------------------------------------------- DBCONTEXT AND DBSET ------------------------------------------------------------------------------- public class AppDbContext : DbContext { public DbSet Orders => Set(); public DbSet Customers => Set(); public AppDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasOne(o => o.Customer) .WithMany(c => c.Orders) .HasForeignKey(o => o.CustomerId); modelBuilder.Entity() .HasIndex(o => o.CreatedAt); } } ------------------------------------------------------------------------------- MIGRATIONS ------------------------------------------------------------------------------- dotnet ef migrations add InitialCreate dotnet ef migrations add AddOrderStatusColumn dotnet ef database update dotnet ef migrations remove dotnet ef migrations script // generates raw SQL dotnet ef database drop (Requires the dotnet-ef tool: dotnet tool install -g dotnet-ef) ------------------------------------------------------------------------------- RELATIONSHIPS ------------------------------------------------------------------------------- One to many: public class Customer { public int Id { get; set; } public List Orders { get; set; } = new(); } public class Order { public int Id { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } = null!; } Many to many (EF Core 5+, no join entity needed for the simple case): public class Student { public int Id { get; set; } public List Courses { get; set; } = new(); } public class Course { public int Id { get; set; } public List Students { get; set; } = new(); } One to one: public class User { public int Id { get; set; } public UserProfile Profile { get; set; } = null!; } public class UserProfile { public int Id { get; set; } public int UserId { get; set; } public User User { get; set; } = null!; } ------------------------------------------------------------------------------- QUERIES ------------------------------------------------------------------------------- var order = await db.Orders.FindAsync(id); var recent = await db.Orders .Where(o => o.CreatedAt > DateTime.UtcNow.AddDays(-7)) .OrderByDescending(o => o.CreatedAt) .ToListAsync(); var withCustomer = await db.Orders .Include(o => o.Customer) .ThenInclude(c => c.Address) .ToListAsync(); var projected = await db.Orders .Select(o => new { o.Id, o.Total, CustomerName = o.Customer.Name }) .ToListAsync(); ------------------------------------------------------------------------------- TRACKING ------------------------------------------------------------------------------- Tracked queries (default) let EF detect changes for SaveChanges(): var order = await db.Orders.FirstAsync(o => o.Id == id); order.Status = OrderStatus.Shipped; await db.SaveChangesAsync(); No-tracking queries are faster for read-only scenarios: var orders = await db.Orders.AsNoTracking().ToListAsync(); ------------------------------------------------------------------------------- TRANSACTIONS ------------------------------------------------------------------------------- using var transaction = await db.Database.BeginTransactionAsync(); try { db.Orders.Add(order); await db.SaveChangesAsync(); db.Inventory.Update(inventoryItem); await db.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; } ------------------------------------------------------------------------------- CONCURRENCY ------------------------------------------------------------------------------- public class Order { public int Id { get; set; } [Timestamp] public byte[] RowVersion { get; set; } = null!; // optimistic concurrency token } try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { // another process modified/deleted the row first; reload and retry } ------------------------------------------------------------------------------- LOADING STRATEGIES ------------------------------------------------------------------------------- Eager loading: .Include(o => o.Customer) -- one query with a JOIN Explicit loading: await db.Entry(order).Reference(o => o.Customer).LoadAsync(); Lazy loading: requires virtual navigation properties plus the Microsoft.EntityFrameworkCore.Proxies package; loads related data automatically on first access, but easy to cause accidental N+1 query patterns. ------------------------------------------------------------------------------- INDEXES AND PERFORMANCE ------------------------------------------------------------------------------- modelBuilder.Entity().HasIndex(o => o.CustomerId); modelBuilder.Entity().HasIndex(o => new { o.CustomerId, o.CreatedAt }); Watch query plans with: dotnet ef migrations script // review generated SQL/index DDL Or log generated SQL directly: builder.Services.AddDbContext(options => options.UseNpgsql(connectionString).LogTo(Console.WriteLine, LogLevel.Information)); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- The N+1 query problem: looping over a collection and accessing a navigation property inside the loop without .Include(), triggering one extra query per row instead of a single JOIN. Forgetting AsNoTracking() on large read-only query results, adding unnecessary change-tracking overhead. Calling .ToList() too early in a LINQ chain (before Where/Select), which pulls the entire table into memory and finishes filtering in C# instead of pushing the filter down into SQL. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain the N+1 problem with a concrete before/after example, and how .Include()/.ThenInclude() (or a projection with .Select()) solves it by folding related data into a single query. ================================================================================ JSON ================================================================================ DESCRIPTION System.Text.Json is the built-in, high-performance JSON library in modern .NET, generally preferred over the third-party Newtonsoft.Json for new code, though Newtonsoft remains common in older codebases and has some features System.Text.Json lacks. ------------------------------------------------------------------------------- SERIALIZATION ------------------------------------------------------------------------------- using System.Text.Json; var person = new Person { Name = "Jake", Age = 30 }; string json = JsonSerializer.Serialize(person); string prettyJson = JsonSerializer.Serialize(person, new JsonSerializerOptions { WriteIndented = true }); ------------------------------------------------------------------------------- DESERIALIZATION ------------------------------------------------------------------------------- string json = "{\"Name\":\"Jake\",\"Age\":30}"; Person? person = JsonSerializer.Deserialize(json); Deserializing to a dynamic-ish document when the shape is unknown: using var doc = JsonDocument.Parse(json); string name = doc.RootElement.GetProperty("Name").GetString()!; ------------------------------------------------------------------------------- OPTIONS ------------------------------------------------------------------------------- var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true }; ------------------------------------------------------------------------------- ATTRIBUTES ------------------------------------------------------------------------------- public class Person { [JsonPropertyName("full_name")] public string Name { get; set; } = ""; [JsonIgnore] public string InternalNotes { get; set; } = ""; [JsonPropertyOrder(1)] public int Age { get; set; } } ------------------------------------------------------------------------------- CUSTOM CONVERTERS ------------------------------------------------------------------------------- public class DateOnlyConverter : JsonConverter { public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => DateOnly.Parse(reader.GetString()!); public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString("yyyy-MM-dd")); } var options = new JsonSerializerOptions(); options.Converters.Add(new DateOnlyConverter()); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Forgetting that System.Text.Json is case-sensitive on property names by default when reading (unlike Newtonsoft), leading to silently null-populated properties instead of an error. Set PropertyNameCaseInsensitive = true if the source JSON's casing is not guaranteed to match exactly. Trying to deserialize into a type with only a parameterized constructor and no parameterless constructor or init/settable properties matching the JSON, which throws at runtime; for records, ensure constructor parameter names match JSON property names (case-insensitively) or add [JsonConstructor]. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know that System.Text.Json is built for performance (low allocation, Span-based parsing) whereas Newtonsoft.Json historically offered more flexibility (looser type handling, more attributes) -- a reasonable trade-off to describe if asked to compare them. ================================================================================ FILE IO ================================================================================ DESCRIPTION System.IO provides both simple static helpers (File, Directory) and stream-based APIs for more control over reading and writing. ------------------------------------------------------------------------------- READING ------------------------------------------------------------------------------- string text = File.ReadAllText("data.txt"); string[] lines = File.ReadAllLines("data.txt"); byte[] bytes = File.ReadAllBytes("data.bin"); string text2 = await File.ReadAllTextAsync("data.txt"); foreach (var line in File.ReadLines("big-file.txt")) // lazy, low memory { Console.WriteLine(line); } ------------------------------------------------------------------------------- WRITING ------------------------------------------------------------------------------- File.WriteAllText("data.txt", "hello world"); File.WriteAllLines("data.txt", new[] { "line1", "line2" }); File.AppendAllText("log.txt", "new entry\n"); await File.WriteAllTextAsync("data.txt", "hello world"); ------------------------------------------------------------------------------- STREAMS ------------------------------------------------------------------------------- using var reader = new StreamReader("data.txt"); string? line; while ((line = await reader.ReadLineAsync()) != null) { Console.WriteLine(line); } using var writer = new StreamWriter("output.txt", append: true); await writer.WriteLineAsync("new entry"); ------------------------------------------------------------------------------- DIRECTORIES AND PATHS ------------------------------------------------------------------------------- Directory.Exists(path) Directory.CreateDirectory(path) Directory.GetFiles(path, "*.txt") Directory.GetDirectories(path) Directory.Delete(path, recursive: true) Path.Combine("folder", "subfolder", "file.txt") Path.GetFileName(fullPath) Path.GetExtension(fullPath) Path.GetDirectoryName(fullPath) Path.GetFullPath(relativePath) ------------------------------------------------------------------------------- FILE MANAGEMENT ------------------------------------------------------------------------------- File.Exists(path) File.Copy(source, dest, overwrite: true) File.Move(source, dest) File.Delete(path) var info = new FileInfo(path); info.Length info.LastWriteTimeUtc ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Using File.ReadAllText/ReadAllLines on very large files, loading the entire content into memory at once. Use File.ReadLines (lazy) or a StreamReader for large files. Forgetting to dispose streams (StreamReader/StreamWriter/FileStream) -- always wrap them in a using statement, since file handles are a limited OS resource. Building file paths with hardcoded "/" or "\\" separators instead of Path.Combine, breaking cross-platform compatibility. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Know when to reach for the simple File.* static helpers (small files, quick scripts) versus Stream-based APIs (large files, fine-grained control, async streaming). ================================================================================ NETWORKING ================================================================================ DESCRIPTION Covers HttpClient for calling web APIs, plus the lower-level socket primitives for TCP/UDP. ------------------------------------------------------------------------------- HTTPCLIENT ------------------------------------------------------------------------------- Reuse a single HttpClient (or use IHttpClientFactory in ASP.NET Core apps) rather than creating a new one per call. public class WeatherApiClient { private readonly HttpClient _client; public WeatherApiClient(HttpClient client) => _client = client; public async Task GetWeatherAsync(string city) { var response = await _client.GetAsync($"/weather?city={Uri.EscapeDataString(city)}"); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } } Registering with IHttpClientFactory in Program.cs: builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.weather.example.com"); client.Timeout = TimeSpan.FromSeconds(10); }); ------------------------------------------------------------------------------- COMMON METHODS ------------------------------------------------------------------------------- client.GetAsync(url) client.PostAsJsonAsync(url, payload) client.PutAsJsonAsync(url, payload) client.DeleteAsync(url) response.EnsureSuccessStatusCode() response.Content.ReadAsStringAsync() response.Content.ReadFromJsonAsync() response.StatusCode response.IsSuccessStatusCode ------------------------------------------------------------------------------- EXAMPLES ------------------------------------------------------------------------------- var payload = new { Name = "Jake", Age = 30 }; var response = await client.PostAsJsonAsync("/api/people", payload); response.EnsureSuccessStatusCode(); var created = await response.Content.ReadFromJsonAsync(); ------------------------------------------------------------------------------- SOCKETS (TCP / UDP) ------------------------------------------------------------------------------- TCP server (bare-bones): var listener = new TcpListener(IPAddress.Any, 5000); listener.Start(); using var client = await listener.AcceptTcpClientAsync(); using var stream = client.GetStream(); var buffer = new byte[1024]; int bytesRead = await stream.ReadAsync(buffer); UDP send/receive: using var udpClient = new UdpClient(); var data = Encoding.UTF8.GetBytes("hello"); await udpClient.SendAsync(data, data.Length, "127.0.0.1", 5000); ------------------------------------------------------------------------------- DNS ------------------------------------------------------------------------------- IPHostEntry entry = await Dns.GetHostEntryAsync("example.com"); foreach (var ip in entry.AddressList) Console.WriteLine(ip); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Creating a new HttpClient per request (either directly or via "using var client = new HttpClient()") under load, which can exhaust available sockets because each disposed HttpClient's underlying connections linger in a TIME_WAIT state. Reuse via IHttpClientFactory or a single static/injected instance. Not calling EnsureSuccessStatusCode() (or checking IsSuccessStatusCode manually), silently proceeding to parse an error response body as if it were a successful one. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain socket exhaustion from HttpClient misuse -- a common real-world .NET production incident and a frequent system- design/practical interview topic. ================================================================================ POSTGRESQL ================================================================================ DESCRIPTION Covers core SQL as used against PostgreSQL, plus the Npgsql .NET driver for connecting directly (outside of EF Core, or for raw SQL inside it). ------------------------------------------------------------------------------- BASIC SQL ------------------------------------------------------------------------------- SELECT id, name, email FROM customers WHERE created_at > NOW() - INTERVAL '7 days'; INSERT INTO orders (customer_id, total) VALUES (1, 99.99) RETURNING id; UPDATE orders SET status = 'shipped' WHERE id = 42; DELETE FROM orders WHERE status = 'cancelled' AND created_at < NOW() - INTERVAL '30 days'; ------------------------------------------------------------------------------- JOINS ------------------------------------------------------------------------------- SELECT o.id, o.total, c.name FROM orders o INNER JOIN customers c ON o.customer_id = c.id; SELECT c.name, COUNT(o.id) AS order_count FROM customers c LEFT JOIN orders o ON o.customer_id = c.id GROUP BY c.name; ------------------------------------------------------------------------------- INDEXES ------------------------------------------------------------------------------- CREATE INDEX idx_orders_customer_id ON orders (customer_id); CREATE INDEX idx_orders_created_at ON orders (created_at DESC); CREATE UNIQUE INDEX idx_customers_email ON customers (email); ------------------------------------------------------------------------------- CONSTRAINTS ------------------------------------------------------------------------------- CREATE TABLE orders ( id SERIAL PRIMARY KEY, customer_id INT NOT NULL REFERENCES customers(id), total NUMERIC(10, 2) NOT NULL CHECK (total >= 0), status TEXT NOT NULL DEFAULT 'pending', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ------------------------------------------------------------------------------- TRANSACTIONS ------------------------------------------------------------------------------- BEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT; -- or ROLLBACK; on failure ------------------------------------------------------------------------------- VIEWS AND STORED PROCEDURES ------------------------------------------------------------------------------- CREATE VIEW active_customers AS SELECT * FROM customers WHERE last_login > NOW() - INTERVAL '30 days'; CREATE OR REPLACE FUNCTION get_order_total(order_id INT) RETURNS NUMERIC AS $$ BEGIN RETURN (SELECT total FROM orders WHERE id = order_id); END; $$ LANGUAGE plpgsql; ------------------------------------------------------------------------------- NPGSQL (C# DRIVER) ------------------------------------------------------------------------------- var connectionString = "Host=localhost;Database=mydb;Username=user;Password=pass"; await using var conn = new NpgsqlConnection(connectionString); await conn.OpenAsync(); await using var cmd = new NpgsqlCommand("SELECT id, name FROM customers WHERE id = @id", conn); cmd.Parameters.AddWithValue("id", 1); await using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { Console.WriteLine(reader.GetString(reader.GetOrdinal("name"))); } EF Core connection string setup: builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Building SQL by string concatenation with user input, opening the door to SQL injection. Always use parameterized queries (cmd.Parameters.AddWithValue or EF Core's parameterized LINQ, never raw string interpolation of user data into SQL text). Forgetting an index on a foreign key column, causing slow JOINs and slow cascading deletes as the table grows. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to explain the difference between INNER JOIN and LEFT JOIN with a concrete example, and why parameterized queries are non-negotiable for any SQL touching user input. ================================================================================ COMMON EXCEPTIONS ================================================================================ DESCRIPTION A quick-reference for the exceptions that come up constantly in day-to-day C# development, what typically causes them, and the standard fix. ------------------------------------------------------------------------------- NULLREFERENCEEXCEPTION ------------------------------------------------------------------------------- Cause: dereferencing a null reference (calling a method or accessing a property/field on a variable that is null). Fix: null-conditional operator (?.), null-coalescing (??), enabling nullable reference types to catch it at compile time, or an explicit null check. ------------------------------------------------------------------------------- INVALIDOPERATIONEXCEPTION ------------------------------------------------------------------------------- Cause: calling a method when the object is in an invalid state for it -- e.g. modifying a collection during foreach, calling Dequeue()/Pop() on an empty Queue/Stack, or calling Single() on a sequence with more than one element. Fix: check preconditions first (Count > 0), or use Try* variants (TryDequeue, TryPop) where available. ------------------------------------------------------------------------------- KEYNOTFOUNDEXCEPTION ------------------------------------------------------------------------------- Cause: indexing a Dictionary with a key that does not exist. Fix: TryGetValue or GetValueOrDefault instead of the indexer. ------------------------------------------------------------------------------- ARGUMENTEXCEPTION / ARGUMENTNULLEXCEPTION / ARGUMENTOUTOFRANGEEXCEPTION ------------------------------------------------------------------------------- Cause: an invalid argument was passed to a method -- null when not allowed, an out-of-range index, or some other invalid value. Fix: validate inputs at the top of a method (ArgumentNullException. ThrowIfNull(value) is the modern shorthand, .NET 6+) and let the caller see a clear, specific error instead of a confusing failure deeper in the call stack. ------------------------------------------------------------------------------- INDEXOUTOFRANGEEXCEPTION ------------------------------------------------------------------------------- Cause: accessing an array or string index outside its valid bounds. Fix: bounds-check before indexing, or use range/index operators ([^1], [..]) that are less error-prone than manual arithmetic. ------------------------------------------------------------------------------- FORMATEXCEPTION ------------------------------------------------------------------------------- Cause: parsing a string into a number/date with int.Parse, double.Parse, DateTime.Parse, etc. when the string is not in the expected format. Fix: use the TryParse variants and handle the false case explicitly instead of relying on try/catch for expected bad input. ------------------------------------------------------------------------------- OVERFLOWEXCEPTION ------------------------------------------------------------------------------- Cause: an arithmetic operation exceeds the range of the target type, inside a "checked" context (checked keyword or set in the project). Fix: use a larger type (long instead of int), or explicitly decide whether overflow should wrap (unchecked) or throw (checked) depending on the situation. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Catching System.Exception broadly across an entire method body instead of catching the specific exception type the code can meaningfully recover from, which hides bugs and makes debugging production issues much harder. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to name the specific exception type for a scenario (dictionary missing key -> KeyNotFoundException, empty stack pop -> InvalidOperationException, etc.) -- interviewers sometimes probe this to confirm hands-on experience versus surface familiarity. ================================================================================ DEBUGGING ================================================================================ DESCRIPTION Tools and techniques for finding and fixing bugs in C#/.NET code, both from an IDE and from the command line. ------------------------------------------------------------------------------- CONSOLE AND TRACE ------------------------------------------------------------------------------- Console.WriteLine($"value: {value}"); Console.Error.WriteLine("something went wrong"); Debug.WriteLine("only compiled in Debug builds"); Debug.Assert(value > 0, "value should always be positive here"); Trace.WriteLine("logged regardless of build configuration"); ------------------------------------------------------------------------------- NAMEOF AND TYPEOF ------------------------------------------------------------------------------- nameof(someVariable) // "someVariable" -- refactor-safe, unlike a hardcoded string typeof(SomeClass) // System.Type object for SomeClass someObject.GetType() // runtime type of an instance someObject.GetType().Name // "SomeClass" Common pairing in argument validation: if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), "must be non-negative"); } ------------------------------------------------------------------------------- BREAKPOINTS AND STEPPING ------------------------------------------------------------------------------- F9 (or clicking the gutter) toggle a breakpoint F5 start debugging / continue F10 step over F11 step into Shift+F11 step out Conditional breakpoints right-click a breakpoint, add a condition expression (e.g. i == 500) so it only stops when true ------------------------------------------------------------------------------- STACK TRACES ------------------------------------------------------------------------------- try { DoSomething(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); // includes full stack trace Console.WriteLine(ex.StackTrace); // just the trace } Reading a stack trace top-to-bottom shows the innermost failing frame first, working outward to the original caller. ------------------------------------------------------------------------------- IMMEDIATE / WATCH WINDOWS ------------------------------------------------------------------------------- Available in Visual Studio and Rider while paused at a breakpoint: Watch window pin an expression to see its value update live as you step through code Immediate window type and execute arbitrary C# expressions against the current paused state (e.g. call a method, inspect a private field) ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Leaving Console.WriteLine debugging statements in committed code instead of using a proper logger (ILogger) that can be filtered by level and routed to real log storage in production. Debugging a Release build, where optimizations can reorder or inline code in ways that make stepping through confusing or inaccurate. Debug in a Debug build configuration. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to describe your actual debugging process for a tricky bug (reproduce reliably, bisect with breakpoints/logging, form a hypothesis, verify it) -- this is a common behavioral/practical interview question independent of language specifics. ================================================================================ DESIGN PATTERNS ================================================================================ DESCRIPTION Common object-oriented design patterns as they typically appear in C# codebases, with idiomatic .NET touches (interfaces, generics, DI) layered on top of the classic Gang of Four shapes. ------------------------------------------------------------------------------- FACTORY ------------------------------------------------------------------------------- public interface IShape { double Area(); } public class Circle : IShape { public double Radius; public double Area() => Math.PI * Radius * Radius; } public class Square : IShape { public double Side; public double Area() => Side * Side; } public static class ShapeFactory { public static IShape Create(string type, double size) => type switch { "circle" => new Circle { Radius = size }, "square" => new Square { Side = size }, _ => throw new ArgumentException($"Unknown shape type: {type}") }; } ------------------------------------------------------------------------------- REPOSITORY ------------------------------------------------------------------------------- public interface IOrderRepository { Task GetByIdAsync(int id); Task AddAsync(Order order); Task SaveChangesAsync(); } public class OrderRepository : IOrderRepository { private readonly AppDbContext _db; public OrderRepository(AppDbContext db) => _db = db; public Task GetByIdAsync(int id) => _db.Orders.FindAsync(id).AsTask(); public Task AddAsync(Order order) { _db.Orders.Add(order); return Task.CompletedTask; } public Task SaveChangesAsync() => _db.SaveChangesAsync(); } ------------------------------------------------------------------------------- BUILDER ------------------------------------------------------------------------------- public class EmailBuilder { private readonly StringBuilder _body = new(); private string _subject = ""; public EmailBuilder WithSubject(string subject) { _subject = subject; return this; } public EmailBuilder AddLine(string line) { _body.AppendLine(line); return this; } public Email Build() => new Email(_subject, _body.ToString()); } var email = new EmailBuilder() .WithSubject("Welcome") .AddLine("Hi Jake,") .AddLine("Thanks for signing up.") .Build(); ------------------------------------------------------------------------------- STRATEGY ------------------------------------------------------------------------------- public interface IDiscountStrategy { decimal Apply(decimal total); } public class NoDiscount : IDiscountStrategy { public decimal Apply(decimal total) => total; } public class PercentOffDiscount : IDiscountStrategy { private readonly decimal _percent; public PercentOffDiscount(decimal percent) => _percent = percent; public decimal Apply(decimal total) => total * (1 - _percent); } public class Checkout { private readonly IDiscountStrategy _discount; public Checkout(IDiscountStrategy discount) => _discount = discount; public decimal GetTotal(decimal subtotal) => _discount.Apply(subtotal); } ------------------------------------------------------------------------------- OBSERVER ------------------------------------------------------------------------------- Typically implemented in C# using events (see DELEGATES AND EVENTS) rather than a hand-rolled Subject/Observer interface pair: public class StockTicker { public event EventHandler? PriceChanged; public void UpdatePrice(decimal newPrice) => PriceChanged?.Invoke(this, newPrice); } ------------------------------------------------------------------------------- DECORATOR ------------------------------------------------------------------------------- public interface INotifier { void Send(string message); } public class EmailNotifier : INotifier { public void Send(string message) => Console.WriteLine($"Email: {message}"); } public class SmsDecorator : INotifier { private readonly INotifier _inner; public SmsDecorator(INotifier inner) => _inner = inner; public void Send(string message) { _inner.Send(message); Console.WriteLine($"SMS: {message}"); } } INotifier notifier = new SmsDecorator(new EmailNotifier()); notifier.Send("Order shipped"); // sends both email and SMS ------------------------------------------------------------------------------- ADAPTER ------------------------------------------------------------------------------- public interface IModernLogger { void Log(string message); } public class LegacyLogger // third-party class you cannot modify { public void WriteToLog(string text) => Console.WriteLine(text); } public class LegacyLoggerAdapter : IModernLogger { private readonly LegacyLogger _legacy; public LegacyLoggerAdapter(LegacyLogger legacy) => _legacy = legacy; public void Log(string message) => _legacy.WriteToLog(message); } ------------------------------------------------------------------------------- SINGLETON ------------------------------------------------------------------------------- In modern .NET, prefer AddSingleton in the DI container over a hand-rolled static singleton, since it stays testable and mockable: builder.Services.AddSingleton(); The classic manual pattern, when DI is not available: public sealed class ConfigManager { private static readonly Lazy _instance = new(() => new ConfigManager()); public static ConfigManager Instance => _instance.Value; private ConfigManager() { } } ------------------------------------------------------------------------------- DEPENDENCY INJECTION ------------------------------------------------------------------------------- See ASPNET CORE section for the three DI lifetimes (Singleton, Scoped, Transient) and constructor injection examples. DI is less a single "pattern" than the general practice this whole cheat sheet's ASP.NET Core examples already lean on throughout. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Reaching for a design pattern because it is "the correct architecture" rather than because it solves a concrete problem in the code at hand -- patterns add indirection, which is only worth it when it buys real flexibility or testability. Implementing a hand-rolled Singleton in an ASP.NET Core app instead of just registering the type with AddSingleton, losing testability and making the dependency invisible to constructors. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- Be ready to give a real example from your own projects for at least Factory, Strategy, Repository, and Decorator -- these four come up disproportionately often relative to the full Gang of Four catalog in day-to-day C#/.NET interviews. ================================================================================ BEST PRACTICES ================================================================================ DESCRIPTION General guidance on modern C#/.NET idioms, naming, performance, and the kind of things that come up in code review and system design interviews alike. ------------------------------------------------------------------------------- NAMING CONVENTIONS ------------------------------------------------------------------------------- PascalCase classes, methods, properties, public fields, namespaces camelCase local variables, method parameters _camelCase private fields (leading underscore is the common .NET convention, though "camelCase without underscore" is also seen) IPascalCase interfaces, always prefixed with a capital I ALL_CAPS rarely used in C#; prefer PascalCase even for constants ------------------------------------------------------------------------------- MODERN C# IDIOMS ------------------------------------------------------------------------------- Prefer var when the type is obvious from the right-hand side; prefer an explicit type when it improves readability for a reader unfamiliar with the method being called. Prefer expression-bodied members for simple one-liners: public int Square(int x) => x * x; Prefer records over classes for immutable data-transfer objects. Prefer pattern matching (is, switch expressions) over long if/else-if chains checking types or ranges. Enable nullable reference types (enable) on new projects to catch a large class of null-reference bugs at compile time instead of runtime. ------------------------------------------------------------------------------- PERFORMANCE ------------------------------------------------------------------------------- Avoid unnecessary allocations in hot paths: prefer Span and ReadOnlySpan over substring/array-copy operations when just reading a slice of existing memory. Use StringBuilder for string concatenation in loops. Avoid LINQ in extremely hot paths where allocation from iterator state machines and closures matters; a plain for loop can be meaningfully faster there, at some cost to readability. Reuse HttpClient instances; never create one per request. Prefer async I/O throughout a call chain rather than mixing blocking calls (.Result, .Wait()) into an otherwise async pipeline. ------------------------------------------------------------------------------- MEMORY ------------------------------------------------------------------------------- Understand generational garbage collection at a high level: Gen 0 (short-lived objects, collected frequently and cheaply), Gen 1 (a buffer between Gen 0 and Gen 2), and Gen 2 (long-lived objects, collected less often but at higher relative cost). Minimizing Gen 0 allocation pressure is usually the highest-leverage GC-related performance improvement available. Dispose IDisposable resources deterministically with using rather than relying on the finalizer, which runs at an unpredictable time (or possibly never, under process termination). ------------------------------------------------------------------------------- CLEAN CODE ------------------------------------------------------------------------------- Keep methods short and focused on one responsibility; a method that needs a comment to explain "what" it does (as opposed to "why") is often a sign it should be split or renamed. Prefer guard clauses (early returns for invalid input) over deeply nested if blocks. Favor composition over inheritance once an inheritance hierarchy starts requiring more than 2-3 levels to express the relationship. Keep public API surface (of a class, of a library) as small as possible; it is much easier to add a new public member later than to remove one without a breaking change. ------------------------------------------------------------------------------- COMMON INTERVIEW QUESTIONS ------------------------------------------------------------------------------- Explain the difference between == and .Equals() for reference types vs value types vs records vs strings. Explain value types vs reference types, and where each lives in memory. Explain async/await and what "the thread returns to the pool during an await" actually means in practice. Explain the SOLID principles with a concrete C# example for each. Explain the difference between abstract classes and interfaces, and when you would choose one over the other. Walk through how garbage collection works at a high level, and what IDisposable/using are for given that .NET already has a GC. ------------------------------------------------------------------------------- COMMON MISTAKES ------------------------------------------------------------------------------- Treating "best practice" as a fixed rulebook rather than a set of trade-offs -- e.g. LINQ readability vs raw loop performance, or interfaces-everywhere vs YAGNI. Being able to articulate the trade-off, not just recite the rule, reads much stronger in an interview than a rule recited without justification. ------------------------------------------------------------------------------- INTERVIEW NOTES ------------------------------------------------------------------------------- When asked an open-ended "what would you improve about this code" question, a strong structure is: correctness issues first (bugs, edge cases), then readability/maintainability, then performance -- in roughly that priority order, unless the prompt specifically asks about one dimension. =============================================================================== LEET CODE PROBLEMS =============================================================================== So I am going to continue on writing my own adding my solved leetcode problems This will help me prepare for job interviews and jog down stuff that I find important over time. ------------------------------------------------------------------------------- REVERSE LINKED LIST ------------------------------------------------------------------------------- public ListNode ReverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; } return prev; } ------------------------------------------------------------------------------ INVERT TREE ------------------------------------------------------------------------------ public TreeNode InvertTree(TreeNode root) { if (root == null) return null; TreeNode temp = root.left; root.left = root.right; root.right = temp; InvertTree(root.left); InvertTree(root.right); return root; } ================================================================================ END OF FILE ================================================================================