[{"data":1,"prerenderedAt":114},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Fcsharp-core\u002Fcollections":3},{"page":4,"siblings":94,"blog":111},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":6,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Fdotnet\u002Fcsharp-core\u002Fcollections.md","Collections",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,4,"\u002Fdotnet\u002Fcsharp-core\u002Fcollections",[23,28,32,36,40,44,48,53,57,61,65,69,73,77,81],{"id":24,"difficulty":25,"q":26,"a":27},"collection-overview","easy","What are the main collection types in .NET and how do you choose between them?",".NET's collection library has types optimised for different access patterns. Picking\nthe right one affects both performance and API clarity.\n\n```csharp\n\u002F\u002F List\u003CT> — dynamic array; O(1) index, O(n) insert\u002Fremove in middle\nvar list = new List\u003Cint> { 1, 2, 3 };\nlist.Add(4);           \u002F\u002F O(1) amortised\nlist.Insert(0, 0);     \u002F\u002F O(n) — shifts elements\n\n\u002F\u002F Dictionary\u003CK,V> — hash map; O(1) average lookup, insert, remove\nvar dict = new Dictionary\u003Cstring, int> { [\"a\"] = 1, [\"b\"] = 2 };\ndict[\"c\"] = 3;\ndict.TryGetValue(\"a\", out int val); \u002F\u002F val = 1\n\n\u002F\u002F HashSet\u003CT> — unordered unique values; O(1) add, remove, Contains\nvar set = new HashSet\u003Cstring> { \"alpha\", \"beta\" };\nset.Add(\"alpha\"); \u002F\u002F no-op, already present\n\n\u002F\u002F Queue\u003CT> — FIFO; Enqueue\u002FDequeue both O(1)\nvar queue = new Queue\u003Cint>();\nqueue.Enqueue(1); queue.Enqueue(2);\nint first = queue.Dequeue(); \u002F\u002F 1\n\n\u002F\u002F Stack\u003CT> — LIFO; Push\u002FPop both O(1)\nvar stack = new Stack\u003Cint>();\nstack.Push(1); stack.Push(2);\nint top = stack.Pop(); \u002F\u002F 2\n\n\u002F\u002F LinkedList\u003CT> — O(1) insert\u002Fremove anywhere, O(n) lookup\nvar ll = new LinkedList\u003Cint>(new[] { 1, 2, 3 });\nll.AddFirst(0); \u002F\u002F O(1)\n```\n\n**Rule of thumb:** Start with `List\u003CT>` for ordered mutable sequences. Use\n`Dictionary\u003CK,V>` for key-based lookup. Use `HashSet\u003CT>` for membership tests.\nOnly switch to specialised types (`LinkedList`, `SortedDictionary`) when profiling\nshows a concrete bottleneck.\n",{"id":29,"difficulty":25,"q":30,"a":31},"list-vs-array","What is the difference between `List\u003CT>` and `T[]` (array)?","Arrays are fixed-size and slightly faster for indexed access. `List\u003CT>` wraps an\narray and automatically resizes when needed, at the cost of a small overhead.\n\n```csharp\n\u002F\u002F Array — fixed length set at creation:\nint[] arr = new int[5];\narr[0] = 1; arr[4] = 5;\n\u002F\u002F arr.Add(6); — does not exist; arrays cannot grow\n\n\u002F\u002F List\u003CT> — dynamic; backed by an internal array:\nvar list = new List\u003Cint>(capacity: 5); \u002F\u002F hint initial capacity\nlist.Add(1); list.Add(2); \u002F\u002F grows automatically when needed\n\n\u002F\u002F Span\u003CT> interop — both support span (zero-copy slice):\nReadOnlySpan\u003Cint> span1 = arr;           \u002F\u002F direct, no allocation\nReadOnlySpan\u003Cint> span2 = list.AsSpan(); \u002F\u002F also zero allocation (since .NET 5)\n\n\u002F\u002F Performance:\n\u002F\u002F Array:  fastest indexed access, lowest memory overhead\n\u002F\u002F List\u003CT>: ~same indexed speed; stores Count + Capacity + array ref\n\n\u002F\u002F API difference:\narr.Length;       \u002F\u002F int, fixed\nlist.Count;       \u002F\u002F int, current element count\nlist.Capacity;    \u002F\u002F backing array size (>= Count)\n\n\u002F\u002F Returning from methods:\n\u002F\u002F Return T[] when size is fixed and caller should not modify it\n\u002F\u002F Return List\u003CT> when caller needs to add\u002Fremove\n\u002F\u002F Return IReadOnlyList\u003CT> to expose indexed access but prevent mutation\n```\n\n**Rule of thumb:** Use arrays (`T[]`) when the size is known at creation time and\nperformance is critical. Use `List\u003CT>` when the collection needs to grow. Return\n`IReadOnlyList\u003CT>` or `IReadOnlyCollection\u003CT>` from public APIs to prevent callers\nfrom modifying your internal state.\n",{"id":33,"difficulty":14,"q":34,"a":35},"dictionary-internals","How does `Dictionary\u003CTKey, TValue>` work internally and what makes a good key?","`Dictionary\u003CTKey, TValue>` is a **hash table**. It calls `key.GetHashCode()` to\nfind a bucket, then uses `key.Equals()` to resolve collisions within the bucket.\nAverage time complexity is O(1) for Get, Add, ContainsKey; worst case O(n) with\nmany hash collisions.\n\n```csharp\n\u002F\u002F Good key: string, int, Guid, record types (structural equality)\nvar d1 = new Dictionary\u003Cstring, int> { [\"a\"] = 1 };\nvar d2 = new Dictionary\u003Cint, string> { [1] = \"one\" };\n\n\u002F\u002F BAD key: mutable reference type with default GetHashCode (pointer-based)\nclass MutableKey { public string Name { get; set; } = \"\"; }\nvar bad = new Dictionary\u003CMutableKey, int>();\nvar key = new MutableKey { Name = \"a\" };\nbad[key] = 1;\nkey.Name = \"b\"; \u002F\u002F mutate after insertion — GetHashCode changes!\nbad.ContainsKey(key); \u002F\u002F may return false — key is in the wrong bucket!\n\n\u002F\u002F Custom equality:\nvar ci = new Dictionary\u003Cstring, int>(StringComparer.OrdinalIgnoreCase);\nci[\"Hello\"] = 1;\nConsole.WriteLine(ci[\"hello\"]); \u002F\u002F 1 — case-insensitive\n\n\u002F\u002F Capacity hint avoids rehashing:\nvar d3 = new Dictionary\u003Cstring, int>(capacity: 1000); \u002F\u002F pre-allocate buckets\n\n\u002F\u002F Safe access patterns:\nif (d1.TryGetValue(\"a\", out int val))   \u002F\u002F preferred — single lookup\n    Console.WriteLine(val);\n\u002F\u002F d1[\"missing\"] — throws KeyNotFoundException!\nint val2 = d1.GetValueOrDefault(\"missing\", 0); \u002F\u002F 0 — safe\n```\n\n**Rule of thumb:** Keys must have stable, consistent `GetHashCode` values. Never\nmutate an object while it is used as a dictionary key. For custom key types,\nimplement `GetHashCode` and `Equals` correctly, or prefer record types which do\nthis automatically.\n",{"id":37,"difficulty":25,"q":38,"a":39},"hashset-vs-list","When should you use `HashSet\u003CT>` over `List\u003CT>`?","`HashSet\u003CT>` is ideal when you need **fast membership tests** or need to ensure\nuniqueness. `Contains` is O(1) for `HashSet` vs O(n) for `List`.\n\n```csharp\nvar list = new List\u003Cint> { 1, 2, 3, 4, 5 };\nvar set  = new HashSet\u003Cint> { 1, 2, 3, 4, 5 };\n\n\u002F\u002F Membership test performance:\nlist.Contains(5); \u002F\u002F O(n) — scans every element\nset.Contains(5);  \u002F\u002F O(1) — hash lookup\n\n\u002F\u002F Uniqueness: HashSet silently ignores duplicates\nset.Add(3); \u002F\u002F returns false, set unchanged\nlist.Add(3); \u002F\u002F always adds — now has two 3s\n\n\u002F\u002F Set operations (no equivalent in List\u003CT>):\nvar a = new HashSet\u003Cint> { 1, 2, 3, 4 };\nvar b = new HashSet\u003Cint> { 3, 4, 5, 6 };\na.IntersectWith(b);  \u002F\u002F a = { 3, 4 }\na.UnionWith(b);      \u002F\u002F a = { 3, 4, 5, 6 }\na.ExceptWith(b);     \u002F\u002F a = {} (b is a superset here)\n\n\u002F\u002F Remove duplicates from a list efficiently:\nvar withDupes = new List\u003Cint> { 1, 1, 2, 2, 3 };\nvar unique = new HashSet\u003Cint>(withDupes).ToList(); \u002F\u002F [1, 2, 3]\n\n\u002F\u002F Sorted variant:\nvar sortedSet = new SortedSet\u003Cint> { 5, 1, 3, 2 };\nConsole.WriteLine(string.Join(\", \", sortedSet)); \u002F\u002F 1, 2, 3, 5 — always ordered\n```\n\n**Rule of thumb:** Use `HashSet\u003CT>` for any \"is X in the set?\" question at scale.\nUse `SortedSet\u003CT>` when you additionally need the elements in order. Use `List\u003CT>`\nwhen order and duplicates both matter.\n",{"id":41,"difficulty":14,"q":42,"a":43},"concurrent-dictionary","What is `ConcurrentDictionary\u003CK,V>` and when do you need it?","`ConcurrentDictionary\u003CK,V>` is a thread-safe dictionary. Unlike `Dictionary\u003CK,V>`\n(which is not thread-safe), it allows concurrent reads and writes without external\nlocking for most operations.\n\n```csharp\nvar cache = new ConcurrentDictionary\u003Cstring, int>();\n\n\u002F\u002F AddOrUpdate — atomic: adds if missing, or updates with a function\ncache.AddOrUpdate(\"hits\", 1, (key, old) => old + 1);\ncache.AddOrUpdate(\"hits\", 1, (key, old) => old + 1); \u002F\u002F now 2\n\n\u002F\u002F GetOrAdd — atomic: returns existing value or adds new one\nint val = cache.GetOrAdd(\"pageSize\", 25); \u002F\u002F adds 25 if not present\n\n\u002F\u002F WARNING: GetOrAdd with factory is NOT atomic — factory may run multiple times\n\u002F\u002F Use GetOrAdd(key, value) when value is cheap\u002Fidempotent\n\u002F\u002F Use Lazy\u003CT> factory pattern for expensive initialisation:\nvar lazyCache = new ConcurrentDictionary\u003Cstring, Lazy\u003CExpensiveObject>>();\nvar lazy = lazyCache.GetOrAdd(\"key\", _ => new Lazy\u003CExpensiveObject>(() => new ExpensiveObject()));\nExpensiveObject obj = lazy.Value; \u002F\u002F Lazy ensures only one instance created\n\n\u002F\u002F TryAdd, TryRemove, TryUpdate — safe, return bool:\ncache.TryAdd(\"x\", 42);\ncache.TryRemove(\"x\", out int removed);\ncache.TryUpdate(\"hits\", 99, currentValue: 2); \u002F\u002F only updates if current == 2\n\n\u002F\u002F Avoid: foreach + mutation (enumerator takes a snapshot — safe but stale)\nforeach (var (k, v) in cache)\n    Console.WriteLine($\"{k}={v}\");\n```\n\n**Rule of thumb:** Use `ConcurrentDictionary` for shared caches, counters, or\nlookup tables accessed from multiple threads. Prefer the atomic methods (`AddOrUpdate`,\n`GetOrAdd`) over read-then-write patterns which reintroduce race conditions.\n",{"id":45,"difficulty":14,"q":46,"a":47},"readonly-collections","What is the difference between `IReadOnlyList\u003CT>`, `IReadOnlyCollection\u003CT>`, and `ImmutableList\u003CT>`?","These three represent different levels of immutability guarantee.\n\n```csharp\n\u002F\u002F IReadOnlyCollection\u003CT> — Count + enumeration, no index access\nIReadOnlyCollection\u003Cint> roc = new List\u003Cint> { 1, 2, 3 };\nConsole.WriteLine(roc.Count); \u002F\u002F 3\n\u002F\u002F roc[0] — no indexer\n\n\u002F\u002F IReadOnlyList\u003CT> — adds indexer (IReadOnlyCollection + indexer)\nIReadOnlyList\u003Cint> rol = new List\u003Cint> { 1, 2, 3 };\nConsole.WriteLine(rol[0]); \u002F\u002F 1\n\u002F\u002F rol.Add(4); — no Add\n\n\u002F\u002F BUT: the underlying list is still mutable!\nvar mutable = new List\u003Cint> { 1, 2, 3 };\nIReadOnlyList\u003Cint> view = mutable;\nmutable.Add(4);\nConsole.WriteLine(view.Count); \u002F\u002F 4 — view reflects change!\n\n\u002F\u002F ImmutableList\u003CT> — truly immutable; returns a new list on each \"mutation\"\nvar immutable = ImmutableList.Create(1, 2, 3);\nvar updated   = immutable.Add(4); \u002F\u002F new list; original unchanged\nConsole.WriteLine(immutable.Count); \u002F\u002F 3\nConsole.WriteLine(updated.Count);   \u002F\u002F 4\n\n\u002F\u002F ImmutableList is thread-safe (no locking needed) but slower than List\u003CT>\n\u002F\u002F Use ImmutableArray\u003CT> for value-type semantics + performance:\nImmutableArray\u003Cint> arr = ImmutableArray.Create(1, 2, 3);\nImmutableArray\u003Cint> arr2 = arr.Add(4); \u002F\u002F arr still [1,2,3]\n```\n\n**Rule of thumb:** Return `IReadOnlyList\u003CT>` from public APIs to prevent callers\nfrom adding\u002Fremoving. Use `ImmutableList\u003CT>` (from `System.Collections.Immutable`)\nwhen you need a *truly* immutable, shareable collection — e.g., in functional code,\nstate management, or thread-safe contexts.\n",{"id":49,"difficulty":50,"q":51,"a":52},"span-memory","hard","What is `Span\u003CT>` and `Memory\u003CT>` and why do they matter for performance?","`Span\u003CT>` is a **stack-only** struct that represents a contiguous region of memory —\narray, stack memory, or unmanaged memory — without copying it. `Memory\u003CT>` is the\nheap-compatible version (can be stored in fields or used across `await`).\n\n```csharp\nint[] array = { 1, 2, 3, 4, 5, 6, 7, 8 };\n\n\u002F\u002F Span — zero-copy slice of the array:\nSpan\u003Cint> slice = array.AsSpan(2, 4); \u002F\u002F elements 2–5: [3,4,5,6]\nslice[0] = 99; \u002F\u002F modifies the ORIGINAL array!\nConsole.WriteLine(array[2]); \u002F\u002F 99\n\n\u002F\u002F Stack allocation — no heap allocation at all:\nSpan\u003Cbyte> stackBuffer = stackalloc byte[256];\nstackBuffer.Fill(0);\n\n\u002F\u002F String slicing without allocation:\nReadOnlySpan\u003Cchar> str = \"Hello, World!\".AsSpan();\nReadOnlySpan\u003Cchar> greeting = str[..5];             \u002F\u002F \"Hello\" — no allocation\nConsole.WriteLine(greeting.ToString());              \u002F\u002F \"Hello\"\n\n\u002F\u002F Memory\u003CT> — async-compatible (Span cannot cross await boundaries):\nasync Task ProcessAsync(Memory\u003Cbyte> buffer)\n{\n    await stream.ReadAsync(buffer);        \u002F\u002F Memory\u003CT> works across await\n    ReadOnlySpan\u003Cbyte> span = buffer.Span; \u002F\u002F access as Span inside sync code\n    \u002F\u002F do something with span\n}\n\n\u002F\u002F High-performance parsing without allocating substrings:\nReadOnlySpan\u003Cchar> csv = \"Alice,30,London\";\nint comma1 = csv.IndexOf(',');\nReadOnlySpan\u003Cchar> name = csv[..comma1]; \u002F\u002F \"Alice\" — no allocation\n```\n\n**Rule of thumb:** Use `Span\u003CT>` to slice and process buffers without allocation\nin synchronous hot paths. Use `Memory\u003CT>` when you need to store the slice or cross\nan `await`. Prefer `ReadOnlySpan\u003Cchar>` over `string.Substring` for zero-allocation parsing.\n",{"id":54,"difficulty":25,"q":55,"a":56},"queue-stack","What are `Queue\u003CT>` and `Stack\u003CT>` and what are common use cases?","`Queue\u003CT>` implements **FIFO** (first-in, first-out). `Stack\u003CT>` implements **LIFO**\n(last-in, first-out). Both are O(1) for their primary operations.\n\n```csharp\n\u002F\u002F Queue\u003CT> — FIFO:\nvar queue = new Queue\u003Cstring>();\nqueue.Enqueue(\"first\");\nqueue.Enqueue(\"second\");\nqueue.Enqueue(\"third\");\n\nConsole.WriteLine(queue.Peek());    \u002F\u002F \"first\" — peek without removing\nConsole.WriteLine(queue.Dequeue()); \u002F\u002F \"first\" — removes and returns\nConsole.WriteLine(queue.Count);     \u002F\u002F 2\n\n\u002F\u002F Stack\u003CT> — LIFO:\nvar stack = new Stack\u003Cstring>();\nstack.Push(\"bottom\");\nstack.Push(\"middle\");\nstack.Push(\"top\");\n\nConsole.WriteLine(stack.Peek()); \u002F\u002F \"top\" — peek without removing\nConsole.WriteLine(stack.Pop());  \u002F\u002F \"top\" — removes and returns\nConsole.WriteLine(stack.Count);  \u002F\u002F 2\n\n\u002F\u002F Real-world use cases:\n\u002F\u002F Queue: task queues, BFS graph traversal, request buffering\nvar bfsQueue = new Queue\u003CTreeNode>();\nbfsQueue.Enqueue(root);\nwhile (bfsQueue.Count > 0)\n{\n    var node = bfsQueue.Dequeue();\n    foreach (var child in node.Children)\n        bfsQueue.Enqueue(child);\n}\n\n\u002F\u002F Stack: undo history, DFS traversal, expression parsing, call-stack simulation\nvar history = new Stack\u003Cstring>();\nhistory.Push(\"open file\");\nhistory.Push(\"type text\");\nstring undone = history.Pop(); \u002F\u002F \"type text\" — last action undone first\n```\n\n**Rule of thumb:** Use `Queue\u003CT>` for work-item processing where order must be\npreserved (FIFO). Use `Stack\u003CT>` for undo, backtracking, or DFS. Use\n`System.Threading.Channels` or `BlockingCollection\u003CT>` for producer-consumer\nqueues across threads.\n",{"id":58,"difficulty":14,"q":59,"a":60},"sorted-collections","What are `SortedDictionary\u003CK,V>` and `SortedList\u003CK,V>` and how do they differ?","Both maintain keys in sorted order. The difference is their internal data structure:\n`SortedDictionary` uses a red-black tree; `SortedList` uses two parallel arrays.\n\n```csharp\n\u002F\u002F SortedDictionary\u003CK,V> — red-black tree; O(log n) all operations\nvar sd = new SortedDictionary\u003Cstring, int>();\nsd[\"banana\"] = 2;\nsd[\"apple\"]  = 1;\nsd[\"cherry\"] = 3;\nforeach (var kv in sd)\n    Console.Write(kv.Key + \" \"); \u002F\u002F apple banana cherry — sorted!\n\n\u002F\u002F SortedList\u003CK,V> — parallel arrays; O(log n) lookup, O(n) insert\u002Fdelete\nvar sl = new SortedList\u003Cstring, int>();\nsl[\"banana\"] = 2;\nsl[\"apple\"]  = 1;\n\n\u002F\u002F SortedList: supports index-based access to keys and values:\nConsole.WriteLine(sl.Keys[0]);   \u002F\u002F \"apple\"\nConsole.WriteLine(sl.Values[0]); \u002F\u002F 1\n\n\u002F\u002F When to choose which:\n\u002F\u002F SortedDictionary: frequent inserts\u002Fdeletes on a large set\n\u002F\u002F SortedList: mostly reads after initial load; memory-efficient for dense data\n\n\u002F\u002F Custom comparer (e.g., case-insensitive keys):\nvar ci = new SortedDictionary\u003Cstring, int>(StringComparer.OrdinalIgnoreCase);\nci[\"Zebra\"] = 1;\nci[\"apple\"] = 2;\nforeach (var k in ci.Keys) Console.Write(k + \" \"); \u002F\u002F apple Zebra (case-insensitive order)\n```\n\n**Rule of thumb:** Use `SortedDictionary` when you have frequent inserts and deletes\nwith a need for sorted iteration. Use `SortedList` when the data is loaded once and\nread many times — it uses less memory and supports index-based access to keys\u002Fvalues.\n",{"id":62,"difficulty":14,"q":63,"a":64},"collection-interfaces","What are the key collection interfaces (`IEnumerable`, `ICollection`, `IList`) and what does each add?","The collection interfaces form a hierarchy, each adding more capabilities.\n\n```csharp\n\u002F\u002F IEnumerable\u003CT> — only: iterate with foreach\nIEnumerable\u003Cint> en = new[] { 1, 2, 3 };\nforeach (var n in en) Console.Write(n);\n\n\u002F\u002F ICollection\u003CT> — adds: Count, Add, Remove, Contains, Clear\nICollection\u003Cint> col = new List\u003Cint> { 1, 2, 3 };\nConsole.WriteLine(col.Count); \u002F\u002F 3\ncol.Add(4);\ncol.Remove(1);\ncol.Contains(2); \u002F\u002F true\n\n\u002F\u002F IList\u003CT> — adds: indexer [], IndexOf, Insert, RemoveAt\nIList\u003Cint> lst = new List\u003Cint> { 1, 2, 3 };\nConsole.WriteLine(lst[0]); \u002F\u002F 1\nlst.Insert(0, 0);          \u002F\u002F insert at position\nlst.RemoveAt(0);           \u002F\u002F remove by index\n\n\u002F\u002F Read-only counterparts:\n\u002F\u002F IReadOnlyCollection\u003CT> = IEnumerable\u003CT> + Count\n\u002F\u002F IReadOnlyList\u003CT>       = IReadOnlyCollection\u003CT> + indexer\n\u002F\u002F IReadOnlyDictionary\u003CK,V> = read-only key-value lookup\n\n\u002F\u002F Accept the most general interface:\nvoid Print(IEnumerable\u003Cint> items) \u002F\u002F accept anything iterable\n{\n    foreach (var i in items) Console.Write(i);\n}\nPrint(new[] { 1, 2 });       \u002F\u002F array — ok\nPrint(new List\u003Cint> { 1 });  \u002F\u002F list — ok\nPrint(Enumerable.Range(1, 5)); \u002F\u002F lazy sequence — ok\n```\n\n**Rule of thumb:** Accept the *most general* interface your method needs (`IEnumerable\u003CT>`\nfor read-only iteration). Return the *most specific* interface the caller needs\n(`IReadOnlyList\u003CT>` for indexed access, `IReadOnlyCollection\u003CT>` just for counting).\n",{"id":66,"difficulty":25,"q":67,"a":68},"linkedlist","When should you use `LinkedList\u003CT>` instead of `List\u003CT>`?","`LinkedList\u003CT>` is a doubly-linked list. It is O(1) for insert and remove at any\nknown node, but O(n) for index-based access (no indexer). `List\u003CT>` is O(1) for\nindexed access but O(n) for insert\u002Fremove in the middle (shifts elements).\n\n```csharp\nvar linked = new LinkedList\u003Cint>(new[] { 1, 2, 3, 4, 5 });\n\n\u002F\u002F O(1) insert before\u002Fafter a known node:\nLinkedListNode\u003Cint>? node3 = linked.Find(3);\nlinked.AddBefore(node3!, 99); \u002F\u002F [1,2,99,3,4,5]\nlinked.AddAfter(node3!, 88);  \u002F\u002F [1,2,99,3,88,4,5]\n\n\u002F\u002F O(1) add at head\u002Ftail:\nlinked.AddFirst(0);  \u002F\u002F [0,1,2,99,3,88,4,5]\nlinked.AddLast(999); \u002F\u002F [0,1,2,99,3,88,4,5,999]\n\n\u002F\u002F O(1) remove any node if you have the reference:\nlinked.Remove(node3!); \u002F\u002F remove '3' — O(1)\n\n\u002F\u002F No indexer — O(n) to reach element by position:\n\u002F\u002F linked[3] — doesn't exist\nvar val = linked.Skip(3).First(); \u002F\u002F O(n)\n\n\u002F\u002F Real use cases:\n\u002F\u002F - Implement LRU cache (remove from middle + add to end = O(1))\n\u002F\u002F - Undo history where you insert\u002Fremove at specific positions\n\u002F\u002F - Queue\u002FDeque where you add\u002Fremove both ends\n```\n\n`LinkedList\u003CT>` vs `List\u003CT>` summary:\n\n| Operation | List\\\u003CT\\> | LinkedList\\\u003CT\\> |\n|-----------|-----------|-----------------|\n| Index by position | O(1) | O(n) |\n| Insert at known node | O(n) | O(1) |\n| Remove at known node | O(n) | O(1) |\n| Memory | Contiguous (cache-friendly) | Scattered (pointer overhead) |\n\n**Rule of thumb:** Start with `List\u003CT>`. Switch to `LinkedList\u003CT>` only when you\nprofile and confirm that frequent mid-list insertion\u002Fremoval is the bottleneck and\nyou can efficiently maintain `LinkedListNode\u003CT>` references.\n",{"id":70,"difficulty":14,"q":71,"a":72},"collection-performance-tips","What are the key performance considerations when working with .NET collections?","Several common mistakes make collection code slower than it needs to be.\n\n```csharp\n\u002F\u002F 1. Pre-size collections when count is known:\nvar list = new List\u003Cint>(capacity: 10_000); \u002F\u002F avoids 13+ resize+copy cycles\nvar dict = new Dictionary\u003Cstring, int>(capacity: 500);\n\n\u002F\u002F 2. Use TryGetValue instead of ContainsKey + indexer (two lookups vs one):\n\u002F\u002F double lookup:\nif (dict.ContainsKey(\"key\")) { var v = dict[\"key\"]; }\n\u002F\u002F single lookup:\nif (dict.TryGetValue(\"key\", out int val)) { \u002F* use val *\u002F }\n\n\u002F\u002F 3. CollectionsMarshal for zero-copy dict value update (.NET 6+):\nref int count = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, \"hits\", out _);\ncount++;  \u002F\u002F in-place increment — no re-hash, no boxing\n\n\u002F\u002F 4. Prefer Span\u003CT> over LINQ for hot-path slicing:\nint[] data = Enumerable.Range(0, 100_000).ToArray();\n\u002F\u002F LINQ allocates enumerator + closure:\nvar sumLinq = data.Skip(100).Take(900).Sum();\n\u002F\u002F Span — stack-only, no allocation:\nint sumSpan = 0;\nforeach (var n in data.AsSpan(100, 900)) sumSpan += n;\n\n\u002F\u002F 5. For string-keyed dictionaries, use the right comparer:\n\u002F\u002F OrdinalIgnoreCase is faster than CurrentCultureIgnoreCase for most use cases\nvar ci = new Dictionary\u003Cstring, int>(StringComparer.OrdinalIgnoreCase);\n\n\u002F\u002F 6. Avoid List\u003CT> when a fixed array suffices:\nstatic int[] ComputeSquares(int n)\n{\n    var result = new int[n]; \u002F\u002F array — exact size, no overhead\n    for (int i = 0; i \u003C n; i++) result[i] = i * i;\n    return result;\n}\n```\n\n**Rule of thumb:** Pre-size collections, use `TryGetValue` over ContainsKey + indexer,\nprefer `Span\u003CT>` for hot buffer operations, and profile before optimising — most\ncode is not collection-bound.\n",{"id":74,"difficulty":14,"q":75,"a":76},"priority-queue","What is `PriorityQueue\u003CTElement, TPriority>` and when do you use it?","`PriorityQueue\u003CTElement, TPriority>` (.NET 6) is a min-heap where each element\nhas an associated **priority**. `Dequeue` always returns the element with the\n**lowest** priority value. It replaces sorted-list workarounds for scheduling and\ngraph algorithms.\n\n```csharp\n\u002F\u002F Elements are (value, priority) pairs; lowest priority is dequeued first\nvar pq = new PriorityQueue\u003Cstring, int>();\npq.Enqueue(\"low task\",    10);\npq.Enqueue(\"high task\",    1);  \u002F\u002F priority 1 = highest\npq.Enqueue(\"medium task\",  5);\n\nConsole.WriteLine(pq.Dequeue()); \u002F\u002F \"high task\"   (priority 1)\nConsole.WriteLine(pq.Dequeue()); \u002F\u002F \"medium task\" (priority 5)\nConsole.WriteLine(pq.Dequeue()); \u002F\u002F \"low task\"    (priority 10)\n\n\u002F\u002F Peek without removing:\npq.Enqueue(\"urgent\", 0);\npq.TryPeek(out string? element, out int priority);\nConsole.WriteLine($\"{element} at priority {priority}\"); \u002F\u002F urgent at priority 0\n\n\u002F\u002F Dijkstra shortest-path skeleton:\nvar distances = new Dictionary\u003Cstring, int>();\nvar frontier  = new PriorityQueue\u003Cstring, int>();\nfrontier.Enqueue(startNode, 0);\n\nwhile (frontier.Count > 0)\n{\n    var (node, dist) = (frontier.Dequeue(), frontier.TryPeek(out _, out int p) ? p : 0);\n    \u002F\u002F Note: TryDequeue returns element and priority together:\n    \u002F\u002F frontier.TryDequeue(out string node, out int dist)\n    foreach (var (neighbor, weight) in Graph[node])\n    {\n        int newDist = dist + weight;\n        if (!distances.TryGetValue(neighbor, out int old) || newDist \u003C old)\n        {\n            distances[neighbor] = newDist;\n            frontier.Enqueue(neighbor, newDist);\n        }\n    }\n}\n```\n\n**Rule of thumb:** Use `PriorityQueue` for Dijkstra, A*, task schedulers, and any\nscenario where you need the cheapest\u002Fmost-urgent item repeatedly. Remember it is a\n**min-heap** — assign smaller numbers to higher-priority items, or negate the priority\nif you need max-heap behaviour.\n",{"id":78,"difficulty":50,"q":79,"a":80},"frozen-collections","What are `FrozenDictionary\u003CK,V>` and `FrozenSet\u003CT>` (.NET 8) and when do you use them?","`FrozenDictionary\u003CK,V>` and `FrozenSet\u003CT>` (.NET 8) are **read-only, highly optimised**\ncollections built from existing data. They pay a higher one-time construction cost to\nproduce a data structure tuned for the specific key set, then deliver faster lookups than\n`Dictionary` or `HashSet` for read-heavy workloads.\n\n```csharp\n\u002F\u002F Build once at startup — pay construction cost once:\nvar lookup = new Dictionary\u003Cstring, int>\n{\n    [\"apple\"]  = 1,\n    [\"banana\"] = 2,\n    [\"cherry\"] = 3,\n}.ToFrozenDictionary(); \u002F\u002F FrozenDictionary\u003Cstring, int>\n\n\u002F\u002F Lookups are typically faster than Dictionary\u003CK,V> for the same data:\nbool found = lookup.TryGetValue(\"banana\", out int value); \u002F\u002F true, value = 2\nConsole.WriteLine(lookup[\"cherry\"]); \u002F\u002F 3\n\n\u002F\u002F FrozenSet\u003CT> for fast membership tests:\nFrozenSet\u003Cstring> allowedRoles =\n    new HashSet\u003Cstring> { \"Admin\", \"Moderator\", \"Editor\" }\n    .ToFrozenSet(StringComparer.OrdinalIgnoreCase);\n\nConsole.WriteLine(allowedRoles.Contains(\"admin\")); \u002F\u002F true\n\n\u002F\u002F Typical use cases:\n\u002F\u002F - Permission sets loaded at startup and queried per request\n\u002F\u002F - Static lookup tables (HTTP status descriptions, currency codes, config keys)\n\u002F\u002F - Feature flag dictionaries\n\n\u002F\u002F Note: FrozenDictionary is immutable — no Add, Remove, or set-indexer.\n\u002F\u002F Rebuild the frozen collection if the source data changes.\nvar updated = sourceDict\n    .Concat(new[] { KeyValuePair.Create(\"durian\", 4) })\n    .ToFrozenDictionary();\n```\n\n**Rule of thumb:** Use `FrozenDictionary` \u002F `FrozenSet` for lookup tables that are\npopulated once (startup, DI registration, config load) and read millions of times at\nruntime. They beat `Dictionary` on lookup speed at the cost of being immutable and\nslightly slower to construct.\n",{"id":82,"difficulty":50,"q":83,"a":84},"channels","What is `System.Threading.Channels` and when should you use it over `ConcurrentQueue`?","`System.Threading.Channels` provides **async-friendly producer-consumer pipelines**.\nUnlike `ConcurrentQueue\u003CT>` (which requires polling or blocking), channels let\nconsumers `await` new items — no spin-loop, no blocking threads.\n\n```csharp\n\u002F\u002F Unbounded channel — producer never blocks:\nChannel\u003Cint> channel = Channel.CreateUnbounded\u003Cint>();\n\nChannelWriter\u003Cint>  writer = channel.Writer;\nChannelReader\u003Cint>  reader = channel.Reader;\n\n\u002F\u002F Producer — runs on one task:\n_ = Task.Run(async () =>\n{\n    for (int i = 0; i \u003C 10; i++)\n    {\n        await writer.WriteAsync(i);\n        await Task.Delay(50);\n    }\n    writer.Complete(); \u002F\u002F signal no more items\n});\n\n\u002F\u002F Consumer — awaits items as they arrive (no polling):\nawait foreach (int item in reader.ReadAllAsync())\n    Console.WriteLine($\"Received: {item}\");\n\n\u002F\u002F Bounded channel — backpressure: producer is throttled when full\nvar bounded = Channel.CreateBounded\u003Cint>(new BoundedChannelOptions(capacity: 100)\n{\n    FullMode = BoundedChannelFullMode.Wait,        \u002F\u002F await until space is available\n    \u002F\u002F Other modes: DropOldest, DropNewest, DropWrite\n});\n\n\u002F\u002F Multiple producers \u002F consumers are safe — channel is thread-safe:\nvar tasks = Enumerable.Range(0, 4)\n    .Select(id => Task.Run(async () =>\n    {\n        while (await reader.WaitToReadAsync())\n            if (reader.TryRead(out int item))\n                Console.WriteLine($\"Consumer {id}: {item}\");\n    }))\n    .ToArray();\nawait Task.WhenAll(tasks);\n```\n\n**Rule of thumb:** Use `Channel\u003CT>` for async producer-consumer pipelines where\nconsumers should `await` work rather than poll or block. Use a **bounded** channel\nto apply backpressure and prevent producers from overwhelming consumers. Only use\n`ConcurrentQueue\u003CT>` when you already have a polling\u002Fspinning loop or need the\nraw enqueue throughput without async overhead.\n",15,null,{"description":11},"C# collections interview questions — List vs Array, Dictionary internals, HashSet, ConcurrentDictionary, Span\u003CT>, and choosing the right type.","dotnet\u002Fcsharp-core\u002Fcollections","C# Core","csharp-core","2026-06-23","NBCvx6xFguCuYh8BzkuMxlSgaP-8BG6a-HKUVQp8Tfk",[95,99,102,106,107],{"subtopic":96,"path":97,"order":98},"Async \u002F Await","\u002Fdotnet\u002Fcsharp-core\u002Fasync-await",1,{"subtopic":100,"path":101,"order":12},"Delegates & Events","\u002Fdotnet\u002Fcsharp-core\u002Fdelegates-events",{"subtopic":103,"path":104,"order":105},"Pattern Matching","\u002Fdotnet\u002Fcsharp-core\u002Fpattern-matching",3,{"subtopic":6,"path":21,"order":20},{"subtopic":108,"path":109,"order":110},"Exception Handling","\u002Fdotnet\u002Fcsharp-core\u002Fexceptions",5,{"path":112,"title":113},"\u002Fblog\u002Fdotnet-collections-list-dictionary-span","Choosing the Right C# Collection",1782244118020]