Menu
CoddyTech

.NET Interview Questions

How a C# program runs: the CLR, the JIT, memory, garbage collection and IDisposable.

10 questions
  1. What is the CLR, and how does a C# program run?

    The CLR (Common Language Runtime) is the .NET virtual machine: it loads assemblies, JIT-compiles their IL to native code, runs the garbage collector and enforces type safety. The JIT compiles each method on its first call; with tiered compilation (default since .NET Core 3.0), hot methods are recompiled with full optimization. Native AOT (.NET 7 and later) needs no JIT at all.

  2. What is the difference between value types and reference types?

    A value type variable holds the data and is copied on assignment; a reference type variable holds a reference to a heap object, and assignment copies the reference. Structs and enums are value types; classes, string, arrays and delegates are reference types.

    Trap: value types do not always live on the stack. An int field of a class lives on the heap inside that object.

  3. How does garbage collection work in .NET?

    The .NET GC is generational and tracing: it marks objects reachable from roots (locals, statics, GC handles), frees the rest and usually compacts the survivors. New objects start in Gen 0, collected often and cheaply; survivors move to Gen 1 and Gen 2, where collections are expensive. Objects of 85,000 bytes or more go to the large object heap, which is usually swept rather than compacted; see the large object heap docs.

  4. What is IDisposable, and what does the using statement do?

    IDisposable.Dispose() releases resources the GC does not manage, such as file handles and connections, at a known moment. A using block compiles to try and finally, so Dispose() runs even on an exception.

    C#
    using System;
    
    class Connection : IDisposable
    {
        private readonly string name;
        public Connection(string name) { this.name = name; Console.WriteLine("open " + name); }
        public void Dispose() { Console.WriteLine("close " + name); }
    }
    
    class Program
    {
        static void Main()
        {
            try
            {
                using (var a = new Connection("A"))
                using (var b = new Connection("B"))
                {
                    throw new Exception("query failed");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("caught: " + ex.Message);
            }
        }
    }

    Resources are disposed in reverse order, before the catch runs. See using statement.

  5. What is the difference between a finalizer and Dispose, and what is the dispose pattern?

    Dispose is called by your code to release resources deterministically; a finalizer (~MyClass()) is called by the GC at some unknown later time, as a safety net.

    class NativeBuffer : IDisposable
    {
        private IntPtr handle;      // unmanaged memory
        private bool disposed;
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);   // finalizer no longer needed
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposed) return;
            if (disposing) { /* dispose other IDisposable fields here */ }
            Marshal.FreeHGlobal(handle); // free unmanaged resources in both paths
            disposed = true;
        }
    
        ~NativeBuffer() { Dispose(false); }
    }
    

    Objects with a finalizer survive an extra GC, so add one only when the class owns an unmanaged handle directly. Modern code wraps the handle in a SafeHandle instead.

  6. What is the difference between an assembly and a namespace?

    An assembly is a versioned unit of deployment, a .dll or .exe with IL and metadata; a namespace is a logical prefix for type names. One assembly can hold many namespaces and one namespace can span assemblies. Only the assembly affects access, through internal.

  7. What is the difference between .NET Framework, .NET Core and .NET?

    .NET Framework is the original Windows-only runtime, stopped at 4.8.x, and its default C# version is 7.3 (see C# language versioning). .NET Core was the cross-platform, open-source rewrite, called .NET from version 5; it gets the latest C# and a yearly release, .NET 10 is the current long-term support version, and .NET 8 support ends in November 2026.

  8. Why must you override GetHashCode when you override Equals?

    Dictionary and HashSet use the hash code to find the bucket and only then call Equals, so equal objects with different hash codes are treated as different.

    C#
    using System;
    using System.Collections.Generic;
    
    class Point
    {
        public int X, Y;
        public Point(int x, int y) { X = x; Y = y; }
    
        public override bool Equals(object obj)
        {
            var p = obj as Point;
            return p != null && p.X == X && p.Y == Y;
        }
        // GetHashCode not overridden: each object keeps its own reference-based hash
    }
    
    class FixedPoint : Point
    {
        public FixedPoint(int x, int y) : base(x, y) { }
        public override bool Equals(object obj) { return base.Equals(obj); }
        public override int GetHashCode() { return X * 31 + Y; }
    }
    
    class Program
    {
        static void Main()
        {
            var broken = new HashSet<Point> { new Point(1, 2), new Point(1, 2) };
            var correct = new HashSet<Point> { new FixedPoint(1, 2), new FixedPoint(1, 2) };
            Console.WriteLine(broken.Count + " " + correct.Count);
        }
    }

    Equal objects must return equal hash codes. Write HashCode.Combine(X, Y), or use a record, which generates both methods.

  9. What is the difference between a shallow copy and a deep copy in C#?

    A shallow copy copies the fields as they are, so reference fields still point at the same objects; a deep copy also copies those objects. MemberwiseClone() is shallow.

    C#
    using System;
    using System.Collections.Generic;
    
    class Order
    {
        public string Id;
        public List<string> Items = new List<string>();
    
        public Order ShallowCopy() { return (Order)MemberwiseClone(); }
    
        public Order DeepCopy()
        {
            var copy = (Order)MemberwiseClone();
            copy.Items = new List<string>(Items);   // copy the list too
            return copy;
        }
    }
    
    class Program
    {
        static void Main()
        {
            var original = new Order { Id = "A1" };
            original.Items.Add("pen");
    
            var shallow = original.ShallowCopy();
            var deep = original.DeepCopy();
            original.Items.Add("book");
    
            Console.WriteLine(shallow.Items.Count + " " + deep.Items.Count);
            Console.WriteLine(ReferenceEquals(shallow.Items, original.Items));
        }
    }

    The shallow copy shares the list and sees the new item (2); the deep copy does not (1).

  10. What is the difference between the stack and the heap in .NET?

    The stack holds method frames (parameters, locals, return addresses), one stack per thread, freed when the method returns. The heap holds class instances, arrays and boxed values, is shared by all threads, and is freed by the GC. Deep recursion ends in StackOverflowException, which cannot be caught.

Coddy programming languages illustration

Learn to code with Coddy

Get started