Menu
CoddyTech

C# Collections and LINQ Interview Questions

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

12 questions
  1. What is the difference between an array, List<T> and ArrayList?

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

    C#
    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main()
        {
            int[] arr = { 3, 1, 2 };
            Array.Sort(arr);
            Console.WriteLine(string.Join(",", arr) + " length " + arr.Length);
    
            var list = new List<int> { 3, 1, 2 };
            list.Add(5);
            list.Remove(1);
            Console.WriteLine(string.Join(",", list) + " count " + list.Count);
        }
    }

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

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

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

  3. How does Dictionary<TKey, TValue> work internally?

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

    C#
    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main()
        {
            string text = "the cat and the hat and the bat";
            var counts = new Dictionary<string, int>();
    
            foreach (var word in text.Split(' '))
            {
                int c;
                counts.TryGetValue(word, out c);   // c is 0 when the key is missing
                counts[word] = c + 1;
            }
    
            Console.WriteLine(counts["the"] + " " + counts["and"]);
            Console.WriteLine(counts.ContainsKey("dog"));
        }
    }

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

    Practice it: Two Sum, First Unique Character in a String

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

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

    C#
    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main()
        {
            var seen = new HashSet<int>();
            int[] nums = { 4, 2, 7, 2, 9 };
    
            foreach (int n in nums)
            {
                if (!seen.Add(n))   // Add returns false when the value is already there
                    Console.WriteLine("duplicate: " + n);
            }
    
            var a = new HashSet<int> { 1, 2, 3 };
            a.IntersectWith(new[] { 2, 3, 4 });
            Console.WriteLine(string.Join(",", a));
        }
    }

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

    Practice it: Contains Duplicate, Intersection of Two Arrays

  5. When does a LINQ Where query actually run?

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

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

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

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

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

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

  7. When does the body of a yield return method start running?

    C#
    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static IEnumerable<int> Gen()
        {
            Console.Write("start ");
            yield return 1;
            Console.Write("mid ");
            yield return 2;
        }
    
        static void Main()
        {
            var g = Gen();
            Console.Write("before ");
            foreach (var x in g)
                Console.Write(x + " ");
            Console.WriteLine();
        }
    }

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

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

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

    C#
    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main()
        {
            var nums = new List<int> { 1, 2, 3, 4, 5, 6 };
            try
            {
                foreach (var n in nums)
                    if (n % 2 == 0) nums.Remove(n);
            }
            catch (InvalidOperationException e)
            {
                Console.WriteLine(e.GetType().Name);
            }
    
            nums = new List<int> { 1, 2, 3, 4, 5, 6 };
            nums.RemoveAll(n => n % 2 == 0);
            Console.WriteLine(string.Join(",", nums));
    
            nums = new List<int> { 1, 2, 3, 4, 5, 6 };
            for (int i = nums.Count - 1; i >= 0; i--)
                if (nums[i] % 2 == 0) nums.RemoveAt(i);
            Console.WriteLine(string.Join(",", nums));
        }
    }

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

    Practice it: Move Zeroes

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

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

    C#
    using System;
    using System.Linq;
    
    class Program
    {
        static void Main()
        {
            string[] words = { "eat", "tea", "tan", "ate", "nat", "bat" };
    
            var groups = words
                .GroupBy(w => new string(w.OrderBy(c => c).ToArray()))
                .OrderByDescending(g => g.Count());
    
            foreach (var g in groups)
                Console.WriteLine(g.Key + ": " + string.Join(" ", g));
        }
    }

    Grouping by sorted letters solves group anagrams. See LINQ.

    Practice it: Group Anagrams, Valid Anagram

  10. What is the difference between Select and SelectMany in LINQ?

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

    C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    class Program
    {
        static void Main()
        {
            var teams = new Dictionary<string, string[]>
            {
                { "backend", new[] { "Ravi", "Meera" } },
                { "frontend", new[] { "Arjun" } }
            };
    
            var counts = teams.Select(t => t.Value.Length);
            Console.WriteLine(string.Join(",", counts));
    
            var everyone = teams.SelectMany(t => t.Value);
            Console.WriteLine(string.Join(",", everyone));
        }
    }
  11. What is the difference between IEnumerable<T> and IQueryable<T>?

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

    // IQueryable: the filter becomes WHERE in SQL, only matching rows come back
    var active = db.Users.Where(u => u.IsActive).ToList();
    
    // AsEnumerable switches to in-memory LINQ: every row is loaded, then filtered
    var slow = db.Users.AsEnumerable().Where(u => u.IsActive).ToList();
    

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

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

    Choose by the operation you do most.

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

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

    Practice it: Implement Queue Using Stacks

Coddy programming languages illustration

Learn to code with Coddy

Get started