📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

What does default mean in C#? Explain default(T), the default literal, default values of structs, enums and reference types, and how default is used in generics and optional parameters.

The default keyword and default values in C# 14: all-zero memory, default(T) and the default literal, struct and enum pitfalls, generic code that returns default, and the other meanings of the word default in C#.

Junior Theory Beginner csharp default enums generics structs types

🎯 Key points

  • default(T) is the all-zero value: null for reference types and Nullable<T>, 0 for numbers, false for bool, '\0' for char
  • The default literal (C# 7.1) infers T from context: int x = default; CancellationToken ct = default
  • default of a struct zeroes every field and does not run a parameterless constructor or field initialisers
  • default of an enum is 0 even if no member has that value; define None = 0 or make 0 meaningful
  • Generic code uses default for 'no value' (FirstOrDefault, TryGetValue out values); with unconstrained T, T? means 'may be default'
  • Compare generic values with EqualityComparer<T>.Default.Equals(x, default), not ==
  • Some structs have an invalid default, such as ImmutableArray<T> (IsDefault) or DateTime.MinValue as a fake 'unset'
  • The keyword also appears in switch default labels, default interface methods and the default(...) operator

⏱ 30-second answer

Every type has a default value: the value you get from all-zero memory. That is null for reference types and nullable types, 0 for numbers, false for bool, and for structs every field zeroed. default(T) gives it explicitly and the default literal infers the type, as in CancellationToken ct = default. Generic code uses it to mean "no value", which is why FirstOrDefault returns 0 rather than null for int. The traps are that default skips struct constructors, gives enums the value 0 even if it is not a defined member, and makes some structs like ImmutableArray<T> unusable until initialised.

🗣 Full answer (senior)

How I would answer this in an interview

default gives you the zero-initialised value of a type: all bits zero. For reference types that is null; for numeric types 0; for bool false; for structs, every field set to its own default. It matters most in generics, where you do not know the type, and in optional parameters, where it is often the only legal default for structs like CancellationToken.

1. default(T) and the default literal

int a = default(int);          // 0 (C# 2 operator form)
int b = default;               // 0 (C# 7.1 literal, target-typed)
string? s = default;           // null
CancellationToken ct = default; // CancellationToken.None
  • default(T) names the type explicitly.
  • The default literal takes its type from the context (assignment target, parameter type, return type). If there is no target type (var x = default;) it does not compile.

2. Default values by type

Type default Notes
int, long, double, decimal 0 / 0.0 / 0m
bool false
char '\0' (U+0000) Not a space
DateTime 0001-01-01T00:00:00 (DateTime.MinValue) A real, valid date — often a bug when stored
Guid Guid.Empty (00000000-...)
TimeSpan TimeSpan.Zero
Enums The value 0, even if no member has value 0
Nullable<T> null (HasValue == false)
Reference types (string, classes, arrays, delegates) null Even if non-nullable with NRT
Structs All fields defaulted, constructors not run
ImmutableArray<T> A "default" (uninitialised) array that throws on most operations Use IsDefault / IsDefaultOrEmpty

3. Structs and default

The CLR can always create a struct with all-zero memory — in arrays (new Money[10]), uninitialised fields, default(Money) — without calling any constructor. Since C# 10 you can declare a parameterless struct constructor and field initialisers, but they run only for new Money(), not for default(Money) or array elements. So:

public readonly struct Money(decimal amount, string currency)
{
    public decimal Amount { get; } = amount;
    public string Currency { get; } = currency ?? throw new ArgumentNullException(nameof(currency));
}

var m = default(Money);   // Amount 0, Currency null - the validation never ran

A well-designed struct must either treat the all-zero value as valid (TimeSpan.Zero) or guard against it (IsDefault).

4. Enums and default

default(OrderStatus) is (OrderStatus)0. If your enum starts at 1 (Pending = 1), default is an unnamed value, which shows up as 0 in JSON, databases and switch statements. Guidance: define a meaningful zero member (None/Unknown = 0), especially for [Flags] enums where None = 0 is required.

5. default in generics

public static T? FirstOrNone<T>(IEnumerable<T> source, Func<T, bool> predicate)
{
    foreach (var item in source)
        if (predicate(item)) return item;
    return default;   // null for reference types, 0 for int, zero struct for structs
}

The problem: for value types, default is indistinguishable from a real value — FirstOrDefault() on List<int> returns 0 for "not found" and for "found 0". Senior solutions:

  • A Try-pattern: bool TryFind(..., [MaybeNullWhen(false)] out T value).
  • Return T? with a where T : struct overload that returns Nullable<T>.
  • .NET 6+ overloads FirstOrDefault(predicate, defaultValue) to choose a sentinel.
  • Comparing generic values to default: EqualityComparer<T>.Default.Equals(value, default) — value == default does not compile for unconstrained T.

6. default in optional parameters

Optional parameters need compile-time constants, and for most structs default is the only option:

public Task<Order?> GetAsync(int id, CancellationToken cancellationToken = default);
public void Schedule(TimeSpan delay = default);   // TimeSpan.Zero
public void Log(DateTime at = default);           // can't write DateTime.UtcNow here

If you need "now" or a computed default, use DateTime? at = null and resolve it inside: var when = at ?? _clock.UtcNow;.

7. A production example

An integration accepted JSON orders where deliveryDate was optional, but the DTO declared DateTime DeliveryDate. Missing values deserialised to 0001-01-01, which passed validation (it is a valid date) and was written to SQL Server datetime2. Downstream, a scheduling job sorted by delivery date and treated those orders as 2,000 years overdue, escalating them to the top of a warehouse queue. The fix was making the property DateOnly?, adding a validation rule rejecting default for required dates, and a database check constraint. The broader lesson: default is a value, not "missing" — use nullable types when absence is meaningful.

🔍 What the interviewer is checking

Checks understanding of the runtime's zero-initialisation guarantee and its design consequences.

Green flags

  • Lists defaults for reference, value, nullable and enum types.
  • Knows default bypasses struct constructors.
  • Mentions the enum-zero and FirstOrDefault ambiguity traps.
  • Uses EqualityComparer<T>.Default in generic code.

Red flags

  • Thinks default(int) is null.
  • Uses magic sentinel values instead of nullable types.
  • Unaware of the other meanings of default (switch label, interface methods).

📚 Learn in depth

1. Why "all bits zero" is the universal default

The CLR guarantees that newly allocated memory is zeroed: heap objects, arrays, and (by default) stack frames via the .locals init flag. Zeroed memory is a valid value for every type:

  • A zeroed reference is null.
  • A zeroed int/double/decimal is 0.
  • A zeroed struct is a struct whose fields are all zeroed.

This is what makes default(T) cheap: the JIT emits an initobj instruction (for structs) or ldnull / ldc.i4.0, often just zeroing a register or using xor reg, reg. It is also why the runtime does not need to call struct constructors for arrays — new Point[1_000_000] is simply a zeroed memory block.

2. default vs new() for structs

public struct Config
{
    public int Retries = 3;          // field initialiser (C# 10)
    public Config() { }              // explicit parameterless ctor
}

var a = new Config();              // Retries = 3 (ctor runs)
var b = default(Config);           // Retries = 0 (no ctor)
var array = new Config[1];
var c = array[0];                  // Retries = 0 (array elements are zeroed, no ctor)

Activator.CreateInstance<T>() and new T() with the new() constraint call the parameterless constructor if one exists. Rule: never rely on a struct constructor running.

C# 11 also made the compiler auto-default struct fields that are not assigned in a constructor (previously error CS0171), so constructors no longer need to assign every field.

3. Default for reference types under NRT

default on a non-nullable reference type produces null with a warning (CS8625 for literal assignment, CS8603 for returns). In generic code, return default; from a method returning T (unconstrained) gives CS8603 under NRT; the fix is a T? return type or [return: MaybeNull].

4. Generic code patterns

static bool IsDefault<T>(T value) => EqualityComparer<T>.Default.Equals(value, default!);
  • For value types the JIT specialises this per type and devirtualises the comparer, so it compiles to a simple comparison — no boxing.
  • value is null works for unconstrained T (always false for non-nullable value types, and the JIT removes the branch entirely).
  • typeof(T).IsValueType checks are constant-folded per instantiation in modern JITs.

5. default and pattern matching / switch

  • case default: is not valid (default in switch statements is the fallback label); to match the default value use a constant or var x when x == default.
  • In switch expressions, _ is the discard, not default.
  • x is default(Money) does not compile for non-constant structs; compare with Equals or check IsDefault.

6. ImmutableArray<T> — a default-value trap in the BCL

ImmutableArray<T> is a struct wrapping a T[]. Its default has a null inner array, so calling Length or enumerating throws NullReferenceException or InvalidOperationException. That is why it exposes IsDefault and IsDefaultOrEmpty, and why record properties of type ImmutableArray<T> should be initialised to ImmutableArray<T>.Empty or [].

ArraySegment<T> and Memory<T> have similar but safe defaults (empty).

7. Serialisation and databases

  • System.Text.Json: missing properties stay at their default unless marked required ([JsonRequired] or the C# required keyword). DefaultIgnoreCondition = WhenWritingDefault omits 0, false, null from output — which breaks clients that rely on explicit false.
  • EF Core: a key property with its CLR default (0, Guid.Empty) is treated as not set, so EF generates a value; this is why inserting an entity with Id = 0 creates a new identity. Sentinel configuration (HasSentinel) exists in EF Core 8+ for non-default sentinels.
  • SQL Server datetime cannot store 0001-01-01 (minimum is 1753); datetime2 can. A DateTime default can therefore either throw or silently store a nonsense date depending on column type.

Try it yourself

  1. Create a struct with a field initialiser and a parameterless constructor; compare new(), default and an array element.
  2. Call FirstOrDefault() on new List<int> { 0 } and on an empty list, and design an API that tells them apart.
  3. Declare ImmutableArray<int> a = default; and call a.Length.
  4. Deserialise {} into a class with an int Quantity property and a required int Quantity property.

💻 Code

using System.Collections.Immutable;

namespace Types;

public enum OrderStatus { Unknown = 0, Pending = 1, Shipped = 2 }   // 0 is meaningful

public struct Money
{
    public decimal Amount;
    public string Currency = "AUD";
    public Money() { }                        // C# 10: runs for new Money(), not for default
}

public static class Defaults
{
    public static void Main()
    {
        Console.WriteLine(default(DateTime));         // 1/01/0001 12:00:00 AM
        Console.WriteLine(default(OrderStatus));      // Unknown
        Console.WriteLine(new Money().Currency);      // AUD
        Console.WriteLine(default(Money).Currency is null);   // True: constructor skipped
        Console.WriteLine(new Money[1][0].Currency is null);  // True

        // FirstOrDefault ambiguity on value types
        int[] scores = [10, 20];
        Console.WriteLine(scores.FirstOrDefault(s => s > 50));      // 0: real or missing?
        Console.WriteLine(scores.FirstOrDefault(s => s > 50, -1));  // -1: explicit sentinel

        // Invalid default struct
        ImmutableArray<int> arr = default;
        Console.WriteLine(arr.IsDefault);                           // True; arr.Length would throw

        Console.WriteLine(IsDefault(0) && IsDefault<string?>(null) && !IsDefault("x"));
    }

    // Generic default comparison
    public static bool IsDefault<T>(T value) => EqualityComparer<T>.Default.Equals(value, default);

    public static async Task SaveAsync(string path, CancellationToken ct = default)
        => await File.WriteAllTextAsync(path, "ok", ct);
}

⚠ Common mistakes

1. Treating default as "missing"

What happens: DateTime, int or Guid properties default when data is absent, and code treats 0 or 0001-01-01 as real values. Why it is wrong: the default is a valid value, so missing data flows through validation, databases and reports. Do instead: use nullable types (DateTime?, int?) when absence is meaningful, or required members.

2. Relying on struct constructors or field initialisers

What happens: a struct validates or initialises fields in its constructor and assumes every instance went through it. Why it is wrong: default(T), arrays and uninitialised fields bypass constructors. Do instead: make the all-zero state valid, or check IsDefault where the struct is consumed.

3. Enums without a zero member

What happens: enum Status { Pending = 1, Paid = 2 }. Why it is wrong: default(Status) is 0, an unnamed value that appears from uninitialised fields and deserialisation and breaks switches. Do instead: add None = 0 / Unknown = 0, and for [Flags] enums always define None = 0.

4. Using FirstOrDefault on value types to detect "not found"

What happens: var id = ids.FirstOrDefault(x => x > 100); if (id == 0) ... Why it is wrong: 0 might be a legitimate value; the result is ambiguous. Do instead: use Any + First, a Try-pattern, or project to nullable (ids.Cast<int?>().FirstOrDefault(...)).

5. Comparing generic values with ==

What happens: if (value == default) in an unconstrained generic method. Why it is wrong: it does not compile for unconstrained T; casting to object compares references and boxes. Do instead: use EqualityComparer<T>.Default.Equals(value, default).

6. Using a computed default in optional parameters

What happens: trying DateTime at = DateTime.UtcNow or TimeSpan timeout = TimeSpan.FromSeconds(30). Why it is wrong: optional defaults must be compile-time constants; only default is allowed for these structs. Do instead: use a nullable parameter (TimeSpan? timeout = null) and resolve it inside the method.

7. Default ImmutableArray<T> fields

What happens: a record or class has an uninitialised ImmutableArray<T> property. Why it is wrong: most operations on a default ImmutableArray<T> throw. Do instead: initialise to [] / ImmutableArray<T>.Empty, and check IsDefaultOrEmpty at boundaries.

8. Writing default dates to SQL Server

What happens: DateTime.MinValue is saved to a datetime column (throws SqlTypeException) or a datetime2 column (stores year 0001). Why it is wrong: either a runtime failure or nonsense data that breaks sorting and reporting. Do instead: use nullable dates, validate before saving, and add database constraints.

9. Omitting defaults in JSON output unintentionally

What happens: DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault is set globally. Why it is wrong: clients stop receiving false, 0 and empty values and may treat missing as different from false. Do instead: use WhenWritingNull globally, and per-property attributes where omission is intended.

10. Inserting entities with default keys unintentionally

What happens: an update is attempted with Id = 0 because a DTO lost its ID. Why it is wrong: EF Core treats the default key as "new" and inserts a duplicate instead of updating. Do instead: validate IDs on update endpoints and use strongly-typed IDs that cannot be accidentally defaulted.

🏛 Senior insight: trade-offs

Seniors design types so that the default value is safe, because the runtime will create it whether you like it or not.

Type design Default-safe approach
Enums Unknown = 0, validate on input
Value objects (struct) zero state valid, or IsDefault check
Optional data T? instead of sentinels
Generic APIs return T? or bool TryX(out T) instead of ambiguous default
Async APIs CancellationToken ct = default as the last parameter
  • Serialisers, EF Core materialisation and arrays all produce defaults without calling your constructors; test those paths.
  • The default literal plus target typing keeps code short, but explicit default(T) can be clearer in generic code reviews.
  • Mentioning [SkipLocalsInit] and the zero-initialisation guarantee shows understanding of the runtime.

🔗 Follow-ups the interviewer may ask

  1. When: The candidate lists null as the default of int?. Why: Default of a nullable value type is null, which links to lifted operators and safe access.
    How to answer

    int? is Nullable<int>, a struct that pairs a value with a HasValue flag, so a value type can represent "no value" (a nullable database column, an optional filter). Reading .Value when it is empty throws, so I use ??, GetValueOrDefault() or if (x is int v). Operators are lifted: null + 1 is null, and comparisons like null > 0 are false. Boxing is special: a null int? boxes to a null reference and a non-null one boxes to a plain boxed int. Unlike string?, int? is a real run-time type.

  2. When: The candidate says default skips struct constructors. Why: Default struct values bypass constructors, which tests struct design understanding.
    How to answer

    A value type holds its data directly, so assigning or passing it copies the data; a reference type variable holds a reference to an object on the managed heap, so copying the variable makes two references to the same object. Value types are stored inline where they are declared: a local usually sits on the stack or in a register, but a struct field of a class lives inside that object on the heap, and captured or boxed values also end up on the heap. By default classes compare by identity and structs by their fields. I use structs for small, immutable values like Money or coordinates, and classes for everything with identity or behaviour.

↩ Usually asked after

More in Types

Last updated 30 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me