C# interviews test knowledge of OOP principles, the .NET runtime, LINQ, async programming, and modern language features. This guide covers 50 of the most common questions — with concise answers and runnable code examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Types & Variables | Value vs reference types, boxing, nullable |
| OOP | Inheritance, abstract vs virtual, interfaces |
| Collections | List, Dictionary, IEnumerable, LINQ |
| Delegates & Events | Func/Action, lambda, event keyword |
| Generics | Constraints, variance, generic collections |
| Async/Await | Task, async/await, ConfigureAwait |
| Memory | IDisposable, GC, using statement |
| Modern C# | Records, pattern matching, nullable refs |
Types and variables
1. What is the difference between value types and reference types?
| Value types | Reference types | |
|---|---|---|
| Storage | Stack (usually) | Heap |
| Copy behaviour | Copies the value | Copies the reference |
| Default value | Zero/false/null (struct) | null |
| Examples | int, bool, struct, enum |
class, interface, string, array |
| Nullable (legacy) | int? / Nullable<int> |
Already nullable |
int a = 10;
int b = a; // b is an independent copy
b = 20;
Console.WriteLine(a); // 10 — unchanged
var list1 = new List<int> { 1, 2 };
var list2 = list1; // both point to same object
list2.Add(3);
Console.WriteLine(list1.Count); // 3 — affected!
2. What is boxing and unboxing? Why is it a performance concern?
Boxing wraps a value type in a System.Object on the heap.
Unboxing extracts the value back out.
int n = 42;
object boxed = n; // boxing — heap allocation
int unboxed = (int)boxed; // unboxing — explicit cast required
Why it matters:
- Boxing allocates on the heap → GC pressure
- Unboxing requires a type check → runtime cost
- Avoid in hot paths; use generics (
List<int>instead ofArrayList)
3. What is the difference between const and readonly?
const |
readonly |
|
|---|---|---|
| Set at | Compile time | Declaration or constructor |
| Instance or static | Always static | Can be instance or static |
| Types allowed | Primitives, string, null | Any type |
| Inlined by compiler | Yes | No |
class Config
{
public const int MaxRetries = 3; // compiled into call sites
public readonly DateTime CreatedAt; // set in ctor
public Config()
{
CreatedAt = DateTime.UtcNow;
}
}
4. What is var and when should you use it?
var tells the compiler to infer the type. The type is still static — it's not dynamic.
var items = new List<string>(); // inferred as List<string>
var result = GetUser(); // inferred from return type
Use var when:
- The right-hand side makes the type obvious
- Working with LINQ results or anonymous types
Avoid var when the type adds clarity (int count = 0 is clearer than var count = 0).
5. What are nullable value types and nullable reference types?
Nullable value type (T? / Nullable<T>): allows null for value types.
int? age = null;
if (age.HasValue)
Console.WriteLine(age.Value);
Console.WriteLine(age ?? 0); // null-coalescing
Nullable reference types (C# 8+, enable in project with <Nullable>enable</Nullable>):
Annotations warn when references might be null.
#nullable enable
string name = "Alice"; // non-nullable, must not be null
string? nickname = null; // nullable, may be null
Console.WriteLine(nickname?.ToUpper() ?? "no nickname");
Object-oriented programming
6. What are the four pillars of OOP?
| Pillar | C# example |
|---|---|
| Encapsulation | Private fields with public properties |
| Inheritance | class Dog : Animal |
| Polymorphism | Virtual methods, method overriding |
| Abstraction | Abstract classes and interfaces |
7. What is the difference between a class and a struct?
| Class | Struct | |
|---|---|---|
| Type | Reference type | Value type |
| Allocation | Heap | Stack (usually) |
| Inheritance | Yes | No (can implement interfaces) |
| Default ctor | Implicit parameterless | Always exists (zeroes fields) |
| Nullable | Yes | Only with Nullable<T> |
| Use case | Complex objects, shared state | Small data bags (Point, Color) |
struct Point { public int X, Y; }
class Circle { public Point Center; public double Radius; }
8. What is the difference between abstract and virtual?
abstract class Shape
{
public abstract double Area(); // no body, MUST be overridden
public virtual string Describe() // has body, CAN be overridden
=> $"Shape with area {Area():F2}";
}
class Circle : Shape
{
public double Radius { get; set; }
public override double Area() => Math.PI * Radius * Radius;
}
abstract |
virtual |
|
|---|---|---|
| Body | None | Has default implementation |
| Override required | Yes | No |
| Class requirement | Method must be in abstract class | Any class |
9. What is the difference between an interface and an abstract class?
| Interface | Abstract class | |
|---|---|---|
| Instantiation | Cannot | Cannot |
| Multiple inheritance | Yes (a class can implement many) | No (single base class) |
| Fields | No (only properties) | Yes |
| Constructor | No | Yes |
| Access modifiers on members | All public by default | Any |
| Default implementations | C# 8+ allowed | Always allowed |
Choose interface when defining a capability (IDisposable, IComparable).
Choose abstract class when sharing implementation across related types.
10. What is method overloading vs method overriding?
Overloading — same name, different parameters (compile-time polymorphism):
int Add(int a, int b) => a + b;
double Add(double a, double b) => a + b;
Overriding — redefines a virtual or abstract method in a subclass (runtime polymorphism):
class Animal { public virtual string Speak() => "..."; }
class Dog : Animal { public override string Speak() => "Woof"; }
11. What does the sealed keyword do?
sealed on a class prevents further inheritance; on a method, prevents further overriding.
sealed class Singleton { /* cannot be subclassed */ }
class Base { public virtual void Foo() {} }
class Child : Base { public sealed override void Foo() {} } // no further override
12. What is the difference between is and as?
object obj = "hello";
// is — returns bool, supports pattern matching
if (obj is string s)
Console.WriteLine(s.Length);
// as — returns null if cast fails (only reference/nullable types)
string? text = obj as string; // null if not string
is with pattern matching is preferred in modern C#.
Properties and indexers
13. What is the difference between a field and a property?
A field is a raw variable. A property is a field with accessor logic.
class Person
{
private string _name = ""; // backing field
public string Name // property with validation
{
get => _name;
set => _name = value?.Trim() ?? throw new ArgumentNullException();
}
public int Age { get; set; } // auto-implemented property
public int BirthYear { get; init; } // init-only (C# 9+)
}
Properties enable encapsulation, validation, lazy loading, and databinding.
LINQ
14. What is LINQ and what are its two syntax forms?
LINQ (Language Integrated Query) provides a unified way to query any IEnumerable<T> or IQueryable<T>.
Query syntax (SQL-like):
var evens = from n in numbers
where n % 2 == 0
orderby n
select n * n;
Method syntax (fluent / lambda):
var evens = numbers
.Where(n => n % 2 == 0)
.OrderBy(n => n)
.Select(n => n * n);
Both compile to the same IL. Method syntax is more composable.
15. What is deferred execution in LINQ?
LINQ queries are not executed when defined — they run when enumerated (with foreach, ToList(), Count(), etc.).
var query = numbers.Where(n => n > 5); // nothing executed yet
numbers.Add(10); // mutation before iteration
var result = query.ToList(); // executes NOW, 10 is included
Force immediate execution: .ToList(), .ToArray(), .ToDictionary(), .First().
16. What is the difference between IEnumerable<T> and IQueryable<T>?
IEnumerable<T> |
IQueryable<T> |
|
|---|---|---|
| Execution | In-memory | Translated (e.g., SQL) |
| Where filters at | After data loaded | Database side |
| Namespace | System.Collections.Generic |
System.Linq |
| Use case | LINQ to Objects | LINQ to SQL / EF Core |
// IQueryable — SQL WHERE is generated
var users = dbContext.Users.Where(u => u.IsActive); // SELECT ... WHERE IsActive=1
// IEnumerable — all rows loaded first, then filtered in C#
var users = dbContext.Users.AsEnumerable().Where(u => u.IsActive); // slow!
17. Common LINQ operators cheat sheet
| Operator | Purpose |
|---|---|
Where |
Filter |
Select |
Project / transform |
SelectMany |
Flatten nested collections |
OrderBy / ThenBy |
Sort ascending |
GroupBy |
Group elements |
Join / GroupJoin |
Inner / outer join |
First / FirstOrDefault |
First element |
Single / SingleOrDefault |
Exactly one element |
Any / All |
Existential checks |
Count / Sum / Average / Min / Max |
Aggregates |
Distinct |
Remove duplicates |
Skip / Take |
Pagination |
Zip |
Combine two sequences pairwise |
ToList / ToArray / ToDictionary |
Materialise |
Delegates, events, and lambdas
18. What is a delegate?
A delegate is a type-safe function pointer.
delegate int MathOp(int a, int b);
MathOp add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // 7
// Multicast
MathOp log = (a, b) => { Console.WriteLine($"{a},{b}"); return 0; };
MathOp combined = add + log;
combined(1, 2);
The BCL provides generic delegates so you rarely define your own:
Action<T>— void returnFunc<T, TResult>— non-void returnPredicate<T>— returns bool
19. What is an event?
An event is a delegate with publish/subscribe semantics — only the class can invoke it; external code can only subscribe/unsubscribe.
class Button
{
public event EventHandler? Clicked; // delegate field with event restriction
public void Click() => Clicked?.Invoke(this, EventArgs.Empty);
}
var btn = new Button();
btn.Clicked += (sender, e) => Console.WriteLine("Clicked!");
btn.Click();
20. What is the difference between Action, Func, and Predicate?
Action<string> print = msg => Console.WriteLine(msg); // void return
Func<int, int, int> add = (a, b) => a + b; // TResult return
Predicate<int> isEven = n => n % 2 == 0; // bool return
Predicate<T> is equivalent to Func<T, bool> but predates generics.
Generics
21. What are generics and why are they useful?
Generics allow writing type-safe code without specifying a concrete type at definition time.
// Without generics (boxing, runtime errors)
ArrayList list = new ArrayList();
list.Add(42);
int n = (int)list[0]; // cast required
// With generics (type-safe, no boxing)
List<int> typed = new List<int>();
typed.Add(42);
int m = typed[0]; // no cast
Benefits: type safety, no boxing, reusability, better IntelliSense.
22. What are generic constraints?
// T must be a class with a parameterless constructor
T Create<T>() where T : class, new() => new T();
// T must implement IComparable<T>
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
// T must be a value type (struct)
void PrintBits<T>(T value) where T : struct { ... }
| Constraint | Meaning |
|---|---|
where T : class |
Reference type |
where T : struct |
Value type |
where T : new() |
Has parameterless ctor |
where T : SomeClass |
Inherits from SomeClass |
where T : IFoo |
Implements interface |
where T : notnull |
Non-nullable type |
Collections
23. What is the difference between Array, List<T>, and LinkedList<T>?
| Array | List<T> |
LinkedList<T> |
|
|---|---|---|---|
| Fixed size | Yes | No (grows) | No |
| Random access | O(1) | O(1) | O(n) |
| Insert/delete at end | O(1) | Amortised O(1) | O(1) |
| Insert/delete in middle | O(n) | O(n) | O(1) (with node ref) |
| Memory | Contiguous | Contiguous | Nodes with pointers |
24. What is the difference between Dictionary<TKey,TValue> and HashSet<T>?
Dictionary: key → value mapping, O(1) average lookupHashSet: unique elements only, O(1) average lookup/insert- Both use hash codes internally
var dict = new Dictionary<string, int> { ["a"] = 1 };
dict.TryGetValue("a", out int v); // preferred over dict["a"] to avoid KeyNotFoundException
var set = new HashSet<int> { 1, 2, 3 };
set.Add(2); // no-op, 2 already present
set.UnionWith(new[] { 3, 4, 5 }); // set operations built in
25. What is IEnumerable<T> vs ICollection<T> vs IList<T>?
IEnumerable<T> — iterate only
└── ICollection<T> — + Count, Add, Remove, Contains
└── IList<T> — + index access [i], IndexOf, Insert
Program to the narrowest interface that meets your needs.
Async and await
26. What is async/await and how does it work?
async/await is syntactic sugar over the Task Parallel Library. The compiler rewrites the method into a state machine.
async Task<string> FetchAsync(string url)
{
using var client = new HttpClient();
string html = await client.GetStringAsync(url); // suspend, don't block thread
return html.Length.ToString();
}
await suspends the method and returns control to the caller until the Task completes. The thread is not blocked — it can serve other work.
27. What is the difference between Task, Task<T>, and ValueTask<T>?
Task |
Task<T> |
ValueTask<T> |
|
|---|---|---|---|
| Return value | None | T |
T |
| Allocation | Heap | Heap | Stack (when sync) |
| Use case | Fire-and-forget style | Async with result | Hot paths / caching |
async Task LogAsync() { await File.AppendAllTextAsync("log.txt", "ok"); }
async Task<int> CountAsync() { return await GetCountFromDbAsync(); }
async ValueTask<int> GetCachedAsync() { return _cache ?? await FetchAsync(); }
28. What does ConfigureAwait(false) do?
By default, await captures the current SynchronizationContext (e.g., UI thread) and resumes there. This is expensive in library code.
// Library code — no UI context needed
public async Task<Data> LoadAsync()
{
var json = await File.ReadAllTextAsync(path).ConfigureAwait(false);
return JsonSerializer.Deserialize<Data>(json)!;
}
ConfigureAwait(false) → resume on any thread pool thread. Use in libraries; usually not needed in ASP.NET Core (no SynchronizationContext there).
29. What is the difference between Task.Run and async/await?
async/await: non-blocking I/O; doesn't use an extra thread for I/O waitsTask.Run: offloads CPU-bound work to a thread pool thread
// I/O-bound — use async/await, no Task.Run needed
string content = await File.ReadAllTextAsync("file.txt");
// CPU-bound — offload to thread pool
int result = await Task.Run(() => Fibonacci(40));
30. How do you run multiple async tasks in parallel?
// Sequential — waits for each one
var a = await GetAAsync();
var b = await GetBAsync();
// Parallel — both start immediately
var taskA = GetAAsync();
var taskB = GetBAsync();
await Task.WhenAll(taskA, taskB);
var (a, b) = (taskA.Result, taskB.Result);
// First one wins
var first = await Task.WhenAny(taskA, taskB);
Exception handling
31. What is the difference between throw and throw ex?
try { RiskyOp(); }
catch (Exception ex)
{
throw; // ✅ preserves original stack trace
throw ex; // ❌ resets stack trace to this line
throw new AppException("Context info", ex); // ✅ wraps with cause
}
Always prefer throw; to rethrow, or throw new ...(ex) to add context.
32. What is the finally block used for?
Code in finally always runs — even if an exception is thrown or a return is hit.
FileStream? fs = null;
try
{
fs = File.OpenRead("data.bin");
Process(fs);
}
finally
{
fs?.Dispose(); // always runs
}
In modern C#, prefer the using statement or using declaration.
33. What are custom exceptions and when should you create them?
Create custom exceptions when callers need to distinguish your failure mode:
public class OrderNotFoundException : Exception
{
public int OrderId { get; }
public OrderNotFoundException(int id)
: base($"Order {id} was not found.") => OrderId = id;
public OrderNotFoundException(int id, Exception inner)
: base($"Order {id} was not found.", inner) => OrderId = id;
}
Inherit from Exception for application exceptions, or ApplicationException (rarely used today).
Memory management
34. What is the IDisposable pattern and when is it needed?
Implement IDisposable when a class holds unmanaged resources (file handles, DB connections, network sockets) or references to other IDisposable objects.
public class FileWriter : IDisposable
{
private StreamWriter? _writer;
private bool _disposed;
public FileWriter(string path) => _writer = new StreamWriter(path);
public void Write(string text) => _writer?.WriteLine(text);
public void Dispose()
{
if (_disposed) return;
_writer?.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}
// Usage
using var fw = new FileWriter("out.txt"); // Dispose called automatically
fw.Write("Hello");
35. How does the .NET garbage collector work?
.NET uses a generational, tracing GC:
| Generation | Contents | Collected |
|---|---|---|
| Gen 0 | New, short-lived objects | Most frequently |
| Gen 1 | Survived one collection | Less often |
| Gen 2 | Long-lived objects | Least often |
| LOH | Objects ≥ 85,000 bytes | Gen 2 collections |
Key points:
- GC runs when Gen 0 is full, under memory pressure, or called explicitly
GC.Collect()— avoid in production; the GC knows better- Finalizers (
~ClassName()) delay collection; preferIDisposable - .NET 5+ uses the new region-based GC in server mode
Strings
36. Why is string immutable and when should you use StringBuilder?
string is a reference type but immutable — every operation creates a new string.
string s = "hello";
s += " world"; // new string allocated, old one eligible for GC
Use StringBuilder for many concatenations in a loop:
var sb = new StringBuilder();
for (int i = 0; i < 10_000; i++)
sb.Append(i).Append(',');
string result = sb.ToString();
For a few concatenations, $"..." interpolation or string.Concat is fine.
Modern C# features
37. What are records (C# 9+)?
Records are immutable reference types with value equality semantics, ideal for DTOs.
record Person(string FirstName, string LastName);
var alice = new Person("Alice", "Smith");
var alice2 = alice with { LastName = "Jones" }; // non-destructive mutation
Console.WriteLine(alice == alice2); // false (value equality)
Console.WriteLine(alice.FirstName); // Alice
Console.WriteLine(alice); // Person { FirstName = Alice, LastName = Smith }
record struct (C# 10) — same but value type.
38. What is pattern matching?
Pattern matching tests values against a shape and binds parts.
object shape = new Circle(5.0);
string desc = shape switch
{
Circle c when c.Radius > 10 => "big circle",
Circle c => $"circle r={c.Radius}",
Rectangle { Width: var w, Height: var h } => $"rect {w}×{h}",
null => "null",
_ => "unknown"
};
Pattern types: type pattern, constant, relational (> 0), logical (and/or/not), property, positional, list (C# 11).
39. What are init-only properties (C# 9+)?
init allows setting a property during object initialization only — then it becomes read-only.
class Order
{
public int Id { get; init; }
public string Item { get; init; } = "";
}
var order = new Order { Id = 1, Item = "Book" };
// order.Id = 2; // compile error after init
40. What are nullable reference types (C# 8+)?
When enabled (<Nullable>enable</Nullable>), the compiler tracks nullability:
string name = "Alice"; // can't be null
string? nickname = null; // may be null
void Greet(string? nick)
{
Console.WriteLine(nick?.ToUpper() ?? "no nickname");
// nick.ToUpper() — warning: possible null dereference
}
Eliminates most NullReferenceExceptions at compile time.
Object equality and comparison
41. What is the difference between == and .Equals()?
For value types: both compare values.
For reference types: == compares references by default; .Equals() can be overridden.
string a = new string("hello".ToCharArray());
string b = new string("hello".ToCharArray());
Console.WriteLine(a == b); // true — string overloads ==
Console.WriteLine(object.ReferenceEquals(a, b)); // false — different objects
Always override Equals and GetHashCode together, and implement IEquatable<T>.
42. What is IComparable vs IComparer?
IComparable<T>: the type knows how to compare itself to another TIComparer<T>: an external object that compares two T instances
class Product : IComparable<Product>
{
public int Price { get; set; }
public int CompareTo(Product? other) => Price.CompareTo(other?.Price);
}
// Custom external comparer
class ByName : IComparer<Product>
{
public int Compare(Product? x, Product? y)
=> string.Compare(x?.Name, y?.Name, StringComparison.Ordinal);
}
products.Sort(new ByName());
Reflection and attributes
43. What are attributes and how are they used?
Attributes add declarative metadata to types, methods, and parameters.
[Obsolete("Use NewMethod instead", error: true)]
void OldMethod() { }
[Serializable]
class Config { }
// Custom attribute
[AttributeUsage(AttributeTargets.Property)]
class RequiredAttribute : Attribute { }
Read at runtime via reflection:
var attrs = typeof(Config).GetCustomAttributes<ObsoleteAttribute>();
Dependency injection and design patterns
44. What is dependency injection and how does ASP.NET Core support it?
DI decouples classes from their dependencies by injecting them rather than constructing them.
// Register in Program.cs
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddSingleton<ICache, MemoryCache>();
// Constructor injection
public class OrderController : ControllerBase
{
private readonly IOrderService _orders;
public OrderController(IOrderService orders) => _orders = orders;
}
Service lifetimes:
| Lifetime | Created | Typical use |
|---|---|---|
Singleton |
Once per app | Caches, config |
Scoped |
Once per HTTP request | DbContext, services |
Transient |
Every injection | Lightweight utilities |
Extension methods
45. What are extension methods?
Extension methods add methods to existing types without modifying them.
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string? s) => string.IsNullOrEmpty(s);
public static string Truncate(this string s, int maxLen)
=> s.Length <= maxLen ? s : s[..maxLen] + "…";
}
"Hello World".Truncate(5); // "Hello…"
Rules: must be in a static class, first parameter prefixed with this. LINQ is built entirely from extension methods.
Miscellaneous
46. What is dynamic and when should it be used?
dynamic bypasses compile-time type checking — resolved at runtime via the DLR.
dynamic obj = GetSomeObject();
obj.DoSomething(); // no compile error, but may throw at runtime
Use sparingly — primarily for COM interop, scripting scenarios, and deserialized JSON with unknown shape. Prefer object + casting or generics for type-safe code.
47. What is the yield keyword?
yield return creates an iterator — values are produced lazily on demand.
IEnumerable<int> EvenNumbers(int max)
{
for (int i = 0; i <= max; i += 2)
yield return i; // pauses, caller gets value
}
foreach (var n in EvenNumbers(10))
Console.Write(n + " "); // 0 2 4 6 8 10
yield break ends the sequence early. Useful for streaming large datasets without loading everything into memory.
48. What is Span<T> and Memory<T>?
Span<T> is a stack-allocated type that provides a window into contiguous memory — arrays, stack memory, or native memory — without allocation.
string csv = "Alice,30,Engineer";
ReadOnlySpan<char> span = csv.AsSpan();
int firstComma = span.IndexOf(',');
ReadOnlySpan<char> name = span[..firstComma]; // "Alice" — no allocation
Memory<T> is the heap-friendly version that can be stored in fields and used in async methods.
Use in high-performance, low-allocation parsing code.
49. What is the difference between == for strings and why does string interning matter?
The .NET runtime interns string literals — identical literals share the same memory address.
string a = "hello";
string b = "hello";
Console.WriteLine(object.ReferenceEquals(a, b)); // true — interned!
string c = new string("hello".ToCharArray()); // forces new allocation
Console.WriteLine(object.ReferenceEquals(a, c)); // false
// Force interning
string d = string.Intern(c);
Console.WriteLine(object.ReferenceEquals(a, d)); // true
For comparison, always use == (which calls Equals for strings) or string.Equals(a, b, StringComparison.Ordinal) — never rely on ReferenceEquals for value equality.
50. What are the most common C# anti-patterns?
| Anti-pattern | Problem | Fix |
|---|---|---|
catch (Exception) and swallow |
Hides bugs | Rethrow or log and rethrow |
async void (non-event) |
Exceptions lost | Return Task |
Blocking on Task with .Result/.Wait() |
Deadlocks in sync contexts | await instead |
new Thread() for async work |
Heavyweight | Task.Run or async/await |
Mutating IEnumerable<T> while iterating |
InvalidOperationException |
Copy to list first |
string += in loop |
O(n²) allocations | StringBuilder |
Ignoring IDisposable |
Resource leaks | using statement |
| God class | Unmaintainable | Single Responsibility Principle |
Common mistakes
| Mistake | Why it breaks | Fix |
|---|---|---|
async void method |
Unobserved exceptions crash the app | async Task |
.Result on Task in ASP.NET |
Deadlock | await |
Not implementing GetHashCode with Equals |
Dictionary / HashSet breaks |
Always pair them |
== on struct without override |
Compares all fields by reflection (slow) | Override == / use record struct |
foreach on IQueryable with no terminal |
N+1 queries | ToList() once, then iterate |
| Capturing loop variable in lambda | Closure captures by ref | Copy: var captured = i; |
Forgetting await in async method |
Returns immediately | Always await or chain |
List<T> exposed as public field |
External code can mutate | Return IReadOnlyList<T> |
C# vs Java vs Python
| Feature | C# | Java | Python |
|---|---|---|---|
| First-class async | async/await (excellent) |
Virtual threads (Java 21) | asyncio |
| Value types on stack | struct |
Primitives only | No |
| Properties | Built-in | Getters/setters | @property |
| LINQ | Built-in | Streams API | List comprehensions |
| Nullable safety | C# 8+ annotations | Optional |
Optional typing |
| Pattern matching | Excellent (C# 8+) | Good (Java 16+) | match (3.10+) |
| Records | record (C# 9+) |
record (Java 16+) |
@dataclass |
| Operator overloading | Yes | No | __dunder__ methods |
Frequently asked questions
Q: Is C# only for Windows?
A: No. .NET 5+ (including .NET 6/7/8/9) is cross-platform — Linux, macOS, Docker. .NET Framework (4.x) is Windows-only.
Q: When should I use a struct vs a class?
A: Use struct for small, immutable data with value semantics (Point, Color, DateTime). Use class for anything larger, mutable, or that needs inheritance.
Q: What is the difference between .NET Framework, .NET Core, and .NET 5+?
A: .NET Framework (Windows-only, legacy). .NET Core (cross-platform, modern, now called just .NET from version 5). .NET 5+ is the unified successor — use it for all new projects.
Q: What is async/await doing under the hood?
A: The compiler rewrites the method into a state machine. Each await point is a state. When resumed, the method continues from that state, freeing the thread between I/O waits.
Q: How do I choose between List<T>, IEnumerable<T>, and IReadOnlyList<T> for method signatures?
A: Accept the narrowest type (usually IEnumerable<T>). Return the most informative type without exposing mutability (IReadOnlyList<T> is better than List<T> for return values).
Q: What is the difference between Task.WhenAll and Task.WaitAll?
A: Task.WhenAll is async — returns a Task, doesn't block the thread. Task.WaitAll is synchronous — blocks the calling thread. Always prefer await Task.WhenAll(...).