A dense, scan-first reference for the common .NET methods and patterns — the kind of thing you could dig out of IntelliSense, but faster when it’s all on one page. Strings, collections, LINQ, the traversal skeletons, and a Big-O table.
Strings
A string is an immutable reference type. You can read s[i] but not assign it; every “change” returns a new string.
string s = "hello";
s.Length; // 5
s[0]; // 'h' (a char) — cannot do s[0] = 'H'
s.Substring(2); // "llo"
s.Substring(1, 3); // "ell" (startIndex, length)
s.IndexOf('l'); // 2 (-1 if not found)
s.IndexOf("lo"); // 3
s.LastIndexOf('l'); // 3
s.Contains("ell"); // true
s.StartsWith("he"); // true
s.EndsWith("lo"); // true
s.Replace("l", "L"); // "heLLo"
s.ToUpper(); s.ToLower();
s.Trim(); s.TrimStart(); s.TrimEnd();
s.Split(','); // string[] on a delimiter
s.Split(' ', StringSplitOptions.RemoveEmptyEntries); // drop empties
string.Join(",", items); // glue an IEnumerable into one string
s.ToCharArray(); // char[]
string.IsNullOrEmpty(s);
string.IsNullOrWhiteSpace(s);
s.PadLeft(5, '0'); // "hello" -> "hello"; "42" -> "00042"
new string('x', 3); // "xxx"
new string(charArray); // build a string from a char[]char helpers
char.IsDigit(c);
char.IsLetter(c);
char.IsLetterOrDigit(c);
char.IsWhiteSpace(c);
char.IsUpper(c); char.IsLower(c);
char.ToUpper(c); char.ToLower(c);
c - '0'; // int value of a digit char: '7' -> 7
(char)('a' + 1); // 'b'StringBuilder — mutable, use it to build strings in a loop
var sb = new StringBuilder();
sb.Append("x");
sb.Append(42);
sb.AppendLine("line");
sb.Insert(0, "start");
sb.Length;
sb[i];
sb.ToString(); // materialize the final stringParsing numbers
int.Parse("42"); // throws FormatException on bad input
int.TryParse("42", out int n); // returns bool; n = 0 on failure (safe)
double.Parse("3.14");
double.TryParse(text, out double d);
Convert.ToInt32("42");
n.ToString();List<T>
var list = new List<int>();
var list = new List<int> { 1, 2, 3 };
list.Add(4);
list.AddRange(new[] { 5, 6 });
list.Insert(0, 9); // insert at index
list[i]; // index get/set — O(1)
list.Count;
list.Remove(2); // remove first occurrence of VALUE 2 -> bool
list.RemoveAt(0); // remove at INDEX
list.RemoveAll(x => x < 0);
list.Contains(3); // O(n)
list.IndexOf(3); // O(n), -1 if absent
list.Sort(); // in place, ascending
list.Sort((a, b) => b - a); // custom comparison (here: descending)
list.Reverse();
list.Clear();
list.ToArray();
list.Find(x => x > 2); // first match, or default(T)
list.FindIndex(x => x > 2); // index, or -1
list.FindAll(x => x > 2); // List<T> of all matches
list.Exists(x => x == 2); // boolHashSet<T> — O(1) membership
var set = new HashSet<int>();
var set = new HashSet<int> { 1, 2, 3 };
set.Add(4); // returns bool — false if already present
set.Remove(2); // returns bool
set.Contains(3); // O(1)
set.Count;Set operations that mutate in place (efficient — prefer these):
a.UnionWith(b); // a becomes a OR b
a.IntersectWith(b); // a becomes a AND b
a.ExceptWith(b); // a becomes a minus b (great for "missing"/"unused")
a.SymmetricExceptWith(b); // elements in exactly one
a.IsSubsetOf(b); a.IsSupersetOf(b); a.Overlaps(b); a.SetEquals(b); // all boolLINQ versions return a new IEnumerable (do NOT mutate, and are NOT a HashSet — materialize with .ToHashSet()):
a.Union(b); a.Intersect(b); a.Except(b); // IEnumerable<T> — add .ToHashSet()Dictionary<K,V> — O(1) lookup by key
var d = new Dictionary<string, int>();
d["a"] = 1; // add OR overwrite
d.Add("b", 2); // throws if key already exists
d["a"]; // get — throws KeyNotFoundException if absent
d.ContainsKey("a");
d.TryGetValue("a", out int v); // bool; v = 0 if absent — the safe getter
d.GetValueOrDefault("z"); // 0 if absent
d.GetValueOrDefault("z", -1); // custom default
d.TryAdd("a", 5); // bool, no throw
d.Remove("a");
d.Count;
d.Keys; d.Values;
foreach (var kvp in d) { var k = kvp.Key; var val = kvp.Value; }
foreach (var (k, val) in d) { /* deconstructed */ }Dictionary iteration order is not guaranteed. Don’t rely on it.
Stack, Queue, PriorityQueue
var st = new Stack<int>(); // LIFO — undo, DFS, backtracking
st.Push(1); st.Pop(); st.Peek(); st.Count; st.Contains(x);
var q = new Queue<int>(); // FIFO — BFS, process-in-order
q.Enqueue(1); q.Dequeue(); q.Peek(); q.Count; q.Contains(x);
var pq = new PriorityQueue<string, int>(); // <element, priority> (.NET 6+)
pq.Enqueue("task", 3); // LOWER priority number is dequeued first
pq.Dequeue(); // remove + return the lowest-priority element
pq.Peek(); pq.Count;
// max-first: negate the priority, or pass a custom Comparer<int> to the ctorArrays (1D and 2D)
int[] a = new int[5]; // zero-initialized
int[] a = { 1, 2, 3 };
a.Length;
Array.Sort(a);
Array.Reverse(a);
Array.IndexOf(a, 3);
Array.Fill(a, 0);
// 2D rectangular grid:
int[,] grid = new int[rows, cols];
grid[r, c];
grid.GetLength(0); // number of rows
grid.GetLength(1); // number of cols
// jagged (array of arrays):
int[][] j = new int[rows][];
j[r] = new int[cols];
j[r][c];LINQ (using System.Linq;)
nums.Where(x => x > 2); // filter
nums.Select(x => x * 2); // map / transform
nums.OrderBy(x => x); // sort ascending (stable)
nums.OrderByDescending(x => x).ThenBy(x => x.Name);
nums.Any(x => x > 2); nums.All(x => x > 2);
nums.Count(x => x > 2);
nums.Sum(); nums.Max(); nums.Min(); nums.Average();
nums.First(x => x > 2); nums.FirstOrDefault(x => x > 99);
nums.Distinct();
nums.Take(3); nums.Skip(2);
nums.Contains(3);
nums.GroupBy(x => x.Key); // IEnumerable<IGrouping<K,T>>; each group IS enumerable
nums.ToDictionary(x => x.Key, x => x.Value);
nums.ToList(); nums.ToArray(); nums.ToHashSet();
nums.Aggregate(0, (acc, x) => acc + x); // fold / reduceLINQ is deferred:
Where/Selectdon’t run until you enumerate (foreach,ToList,Count…).ToList/ToArrayforce it once, now.
Common patterns
Frequency count:
var freq = new Dictionary<char, int>();
foreach (char c in s)
freq[c] = freq.GetValueOrDefault(c) + 1;Bucket / group into lists:
var groups = new Dictionary<string, List<int>>();
if (!groups.ContainsKey(key)) groups[key] = new List<int>();
groups[key].Add(value);Grid: visit the 4 neighbors (with bounds check):
int[] dr = { -1, 1, 0, 0 };
int[] dc = { 0, 0, -1, 1 };
for (int k = 0; k < 4; k++)
{
int nr = r + dr[k], nc = c + dc[k];
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
// visit grid[nr, nc]
}Sort objects by a field:
people.Sort((a, b) => a.Age.CompareTo(b.Age)); // in place
var sorted = people.OrderBy(p => p.Age).ThenBy(p => p.Name).ToList(); // new listReverse a string:
var reversed = new string(s.Reverse().ToArray());Traversal skeletons
DFS — recursive (the call stack IS the stack):
var seen = new HashSet<Node>();
Visit(start);
void Visit(Node node)
{
if (!seen.Add(node)) return; // visited check — cycle-SAFE (won't loop forever)
// pre-order work here
foreach (var child in node.Children)
Visit(child);
// post-order work here (e.g. add to a build order)
}To detect a cycle (not merely survive it), add a second set tracking the current path:
var finished = new HashSet<Node>();
var onPath = new HashSet<Node>();
void Visit(Node node)
{
if (finished.Contains(node)) return;
if (!onPath.Add(node)) throw new InvalidOperationException("cycle at " + node);
foreach (var child in node.Children)
Visit(child);
onPath.Remove(node); // step off the path on the way back up
finished.Add(node);
}
seen/finished= won’t loop forever (safety).onPath= notices the loop (detection). Different sets, different jobs.
BFS — queue + while loop (no recursion). Use for shortest / fewest / level-by-level:
var seen = new HashSet<Node>();
var q = new Queue<Node>();
seen.Add(start);
q.Enqueue(start);
while (q.Count > 0)
{
var node = q.Dequeue(); // process on dequeue (no post-order moment)
foreach (var child in node.Children)
if (seen.Add(child)) // mark seen when ENQUEUING
q.Enqueue(child);
}Big-O cheat table
| Operation | List | HashSet | Dictionary | Stack/Queue |
|---|---|---|---|---|
| Add / Push / Enqueue | O(1)* | O(1) | O(1) | O(1) |
| Remove by value | O(n) | O(1) | O(1) by key | — |
| Contains / lookup | O(n) | O(1) | O(1) | O(n) |
Index access [i] | O(1) | — | O(1) by key | — |
\* amortized (List occasionally resizes its backing array)
| Algorithm | Time | When |
|---|---|---|
| Linear scan | O(n) | unsorted, one-off find |
| Binary search | O(log n) | sorted input |
Sort (Array.Sort/OrderBy) | O(n log n) | general ordering |
| DFS / BFS traversal | O(V + E) | reach/order/components |
| Topological sort | O(V + E) | dependency / build order |
Trigger → tool
| The signal in the problem | Reach for |
|---|---|
| “seen it? / unique? / does X exist?” | HashSet (O(1) membership) |
| “count / group / look up by key” | Dictionary |
| “fewest steps / nearest / level-by-level” | Queue → BFS |
| “undo / backtrack / matching / most-recent” | Stack → DFS |
| “depends on / build order / what breaks if X changes” | graph + topological sort (DFS post-order) |
| “parent / child / ancestor / hierarchy” | tree traversal |
| “the input is sorted” | binary search / two pointers |
| “always take the smallest/most-urgent next” | PriorityQueue |
| reverse lookup is slow / “who points at me?” | build the reverse map once (transpose) |
nested list.Contains in a loop (O(n²)) | swap the inner scan for a HashSet → O(n) |
Reference doc — author-written, not generated at lookup time. The thinking stays yours.