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

📚 Interview question

What are nested types in C#? Explain how a nested class differs from a Java inner class, what it can access in the outer type, default accessibility, and when nested types are a good or a bad design choice.

Nested classes in C#: access to outer private members, no implicit outer instance, private default accessibility, generics, BCL examples and design guidelines.

Mid-Level Theory Intermediate api-design csharp encapsulation nested-types oop

🎯 Key points

  • A nested type is a member of its outer type and defaults to private accessibility
  • It can access all outer members, including private ones, through a reference to an outer instance
  • C# has no implicit outer instance, unlike Java inner classes; pass the outer object in if needed
  • Types nested in generic classes can use the outer type parameters and differ per closed type
  • Metadata and stack traces name it Outer+Inner
  • Good uses: private implementation helpers, enumerators, builders, hiding an implementation behind an interface
  • Avoid publicly visible nested types that callers must reference (CA1034)
  • Do not nest only for grouping; use namespaces

⏱ 30-second answer

A nested type is a class, struct, record, interface, enum or delegate declared inside another type. It is a member of the outer type, so its default accessibility is private, and it can access all members of the outer type, including private ones. Unlike a Java inner class, a C# nested class has no implicit reference to an outer instance: to use outer instance members you must pass the outer object in. Nested types are good for private implementation details (an enumerator, a comparer, a state object, a builder) and for tightly coupled helpers. Public nested types are usually discouraged (CA1034) because callers must write Outer.Inner and the type cannot be moved without breaking them.

🗣 Full answer (senior)

How I would answer this in an interview

A nested type is a class, struct, record, interface, enum or delegate declared inside another type. In C#, a nested class is like Java's static nested class, not an inner class: it has no implicit reference to an instance of the outer type. I'd cover what it can access, default accessibility, and when nesting is good or bad design.

1. Nested vs Java inner classes

public sealed class Order
{
    private decimal _discount;

    public sealed class Line           // nested type
    {
        public decimal Price(Order order) => Amount - order._discount;   // needs an Order passed in
        public decimal Amount { get; init; }
    }
}

var line = new Order.Line { Amount = 10m };   // created without any Order instance
  • In Java, a non-static inner class instance captures Outer.this implicitly. C# has no such capture — if the nested type needs an outer instance, you pass it explicitly (constructor or parameter).
  • This avoids hidden references that keep outer objects alive (a common Java memory-leak source) and makes dependencies explicit.

2. What a nested type can access

  • A nested type can access all members of its containing type, including private ones — static members directly, instance members through an outer instance reference.
  • The outer type can only access the nested type's members according to their accessibility (it doesn't get special access to the nested type's privates).
  • Nested types inherit the outer type's generic type parameters: Outer<T>.Inner can use T.

This privileged access is the main reason to nest: a helper that needs the outer type's private state, without exposing that state publicly.

3. Default accessibility

Declaration Default accessibility
Top-level type internal
Nested type private
Class/struct members private
Interface members public

A nested type can be given any accessibility (public, protected, internal, private protected...). Public nested types are referenced as Outer.Inner.

4. Good uses

  • Private implementation details: enumerators (List<T>.Enumerator), state-machine helpers, comparers, builders used only by the outer type.
  • Types that need privileged access to private state: e.g. a Builder that sets private fields of the object it builds, or an iterator over private storage.
  • Closed hierarchies / discriminated-union style results:
public abstract record PaymentResult
{
    private PaymentResult() { }                      // only nested types can derive
    public sealed record Approved(string AuthCode) : PaymentResult;
    public sealed record Declined(string Reason) : PaymentResult;
    public sealed record Failed(Exception Error) : PaymentResult;
}

The private constructor plus nested derived records makes the hierarchy closed: nobody outside can add a new subtype. That's a common pattern until C# gets native union types.

  • Strongly grouped constants or options that only make sense with the outer type (HttpClientFactoryOptions-style nesting is used sparingly in the BCL).

5. Bad uses

  • Public nested types used widely elsewhere: callers must write Outer.Inner everywhere, and the design guidelines recommend against public nested types except in specific cases (CA1034).
  • Nesting as a namespace substitute: grouping unrelated types inside a class just to organise them — use namespaces or folders.
  • Deep nesting: A.B.C.D is hard to read and discover.
  • Nested types that would be useful independently: they become awkward to reuse and to test.
  • Mutating outer private state from many nested types: it spreads the outer type's invariants across multiple classes.

6. A production example

A payments module returned results as object plus status codes, and callers used switch on strings. We modelled outcomes as a closed nested-record hierarchy (PaymentResult.Approved/Declined/Failed) with a private base constructor. API mappers switched on the types with pattern matching, and because nobody outside the file could add a new result type, reviewers knew the set of cases was complete. When we did add RequiresAction for 3-D Secure, it was in one place and the compiler's discard-arm warnings plus tests guided updates to every switch.

🔍 What the interviewer is checking

Checks precise knowledge of nested type rules and design judgement about public surface area.

Green flags: private by default; access to outer privates; no implicit outer instance and the Java contrast; generic nesting; BCL enumerator example; CA1034 and when public nesting is acceptable; returning a private nested class as an interface.

Red flags: thinks nested classes have an outer this; uses nesting for namespacing; cannot name a good use case.

📚 Learn in depth

1. Representation in metadata

Nested types are stored with a NestedClass table entry linking them to their enclosing type. Their full metadata name uses +: MyApp.Order+Line (that's what you see in Type.FullName, stack traces and reflection). In C# source you write Order.Line. typeof(Order.Line).IsNested is true; DeclaringType returns Order.

2. Accessibility checks

The CLR enforces accessibility, and nested types are considered "inside" their enclosing type for private access. That's why a nested class can access private fields of the outer type — the access check treats the nested type's code as part of the outer type. For nested-in-generic types, Outer<int>.Inner and Outer<string>.Inner are distinct types.

3. Private constructors and closed hierarchies

public abstract class Shape
{
    private Shape() { }
    public sealed class Circle(double r) : Shape { public double R { get; } = r; }
    public sealed class Square(double s) : Shape { public double S { get; } = s; }
}

Because the base constructor is private, only nested types can call it, so the set of subclasses is fixed. Pattern matching still requires a discard arm (the compiler doesn't treat the hierarchy as closed), but the design communicates and enforces closure. Some analysers and libraries (e.g. Dunet, OneOf) build on similar patterns.

4. Nested types and generics

public class Cache<TKey, TValue>
{
    private sealed class Entry { public TValue Value = default!; public DateTimeOffset Expires; }
    private readonly Dictionary<TKey, Entry> _entries = new();
}

Entry automatically uses TValue. Each closed generic gets its own Entry type. This is neat for private helpers; for public nested types in generic classes, callers must write Cache<string, int>.Entry, which is clunky.

5. BCL examples

  • List<T>.Enumerator, Dictionary<TKey,TValue>.KeyCollection — public nested types tightly coupled to their outer type (exposed for performance and foreach pattern binding).
  • Environment.SpecialFolder — a nested enum.
  • HttpClientHandler internals use many private nested helpers.

The guidelines allow public nested types when they're rarely referenced by name or tightly bound to the outer type (enumerators), and discourage them for commonly used types.

6. Nested types vs file-scoped types

C# 11's file access modifier (file class Helper) restricts a top-level type to its source file — another way to hide helpers without nesting, especially useful for source generators to avoid name collisions. Use nesting when the helper needs the outer type's private members; use file when it just needs to be invisible elsewhere.

Try it yourself

  1. Print typeof(Order.Line).FullName and observe the +.
  2. Access a private field of the outer type from a nested class, then try the reverse.
  3. Build a closed result hierarchy with a private base constructor and try to derive from it outside.
  4. Replace a private nested helper with a file-scoped class and compare.

💻 Code

public sealed class RateLimiter(int permits)
{
    private int _available = permits;
    private readonly Lock _lock = new();      // System.Threading.Lock (.NET 9+)

    public IDisposable? TryAcquire()
    {
        lock (_lock)
        {
            if (_available == 0) return null;
            _available--;
            return new Lease(this);              // callers only see IDisposable
        }
    }

    // Private nested type: an implementation detail that can change freely
    private sealed class Lease(RateLimiter owner) : IDisposable
    {
        private int _disposed;
        public void Dispose()
        {
            if (Interlocked.Exchange(ref _disposed, 1) == 1) return;
            lock (owner._lock) owner._available++;   // reads outer PRIVATE fields
        }
    }
}

// Generic outer type: nested type uses outer type parameters
public sealed class LruCache<TKey, TValue>(int capacity) where TKey : notnull
{
    private sealed class Node(TKey key, TValue value)
    {
        public TKey Key { get; } = key;
        public TValue Value { get; set; } = value;
    }
    private readonly Dictionary<TKey, LinkedListNode<Node>> _map = new(capacity);
    private readonly LinkedList<Node> _order = new();
}

⚠ Common mistakes

1. Expecting Java inner-class behaviour

What happens: using outer instance members from a nested class without a reference. Why it is wrong: C# nested types have no implicit outer instance. Do instead: pass the outer instance explicitly when needed.

2. Public nested types for widely used concepts

What happens: Order.Line, Order.Status used throughout the codebase. Why it is wrong: verbose references and poor discoverability (CA1034). Do instead: make commonly used types top-level in a sensible namespace.

3. Using nesting as namespaces

What happens: static classes containing many unrelated nested types for organisation. Why it is wrong: misuse of types for grouping; awkward references. Do instead: use namespaces and folders.

4. Deep nesting

What happens: three or more levels of nested types. Why it is wrong: hard to read, navigate and test. Do instead: keep nesting to one level and extract types.

5. Spreading invariants across nested types

What happens: several nested classes modify the outer type's private fields. Why it is wrong: the outer type no longer controls its own invariants. Do instead: keep state changes in the outer type's methods; nested helpers call them.

6. Forgetting nested types default to private

What happens: a nested type is unexpectedly inaccessible from other classes. Why it is wrong: confusion about the default private accessibility. Do instead: specify accessibility explicitly.

7. Open hierarchies where closed ones are intended

What happens: result types are public abstract classes anyone can extend. Why it is wrong: callers can't rely on a fixed set of cases. Do instead: use a private base constructor with nested sealed subtypes.

8. Nesting types that need independent tests or reuse

What happens: a useful parser or calculator is nested privately. Why it is wrong: can't reuse or test it directly. Do instead: make it a top-level internal type.

🏛 Senior insight: trade-offs

Seniors use nested types to shrink public surface area and keep implementation details private.

Use case Senior choice
Helper used only by one class Private nested class
Return an implementation behind an interface Private nested class returned as the interface
Allocation-free enumeration Public nested struct enumerator (BCL pattern)
Builder strongly bound to one immutable type Public nested Builder is acceptable
Type used widely by callers Top-level type

They also mention that a private nested type can access the outer's private state, which is a controlled way to share internals without making them internal to the whole assembly.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate cites List<T>.Enumerator Why: Probes List internals and allocation-free foreach
    How to answer

    List<T> wraps a T[] plus a _size count and a _version counter. When the array is full, Add allocates a new array twice as large (starting at 4) and copies, so Add is amortised O(1). Insert and Remove shift elements, so they are O(n). Every change increments _version; the enumerator checks it on each MoveNext and throws InvalidOperationException if the list changed. In hot paths, pre-size with the constructor or EnsureCapacity, and use CollectionsMarshal.AsSpan to work on the backing array without copying (but never add or remove while holding that span).

  2. When: Candidate hides an implementation in a private nested class Why: Checks encapsulation reasoning
    How to answer

    Encapsulation means an object owns its state and only lets it change through behaviour that keeps it valid. In C# you keep fields private, expose read access through properties (often get-only or private set), and offer methods like Withdraw(amount) that check rules before changing state, instead of a public setter on Balance. Collections are exposed as IReadOnlyList<T> with Add... methods. The benefit is that invariants ("balance never goes below the overdraft limit") are enforced in one place, so callers cannot put the object into an invalid state, and the internals can change without breaking callers.

🛠 Real-world problems: how you handle them

Breaking change from moving a public nested type
Situation
A shared library exposed `RateLimiter.Lease` as a public nested class that [six] services created and stored in fields.
Signals
When we moved `Lease` to its own file as a top-level type during a refactor, all six services failed to compile on upgrade, and one service's reflection-based config failed at run time looking for `RateLimiter+Lease`.
Investigation
Found every reference to `RateLimiter.Lease` and the string type name in configuration.
Resolution
Reverted for the patch release. In the next major version, `TryAcquire` returned `IDisposable?` and the lease became a private nested class, so its type was no longer part of the public API.
Prevention
Enabled CA1034 and the public API analyser so public nested types and API changes show up in review.

How to tell it (STAR)

**Situation:** a refactor moved a public nested type in our shared rate-limiter library and broke [six] consuming services. **Task:** fix the break and stop the API from leaking implementation types. **Action:** I reverted in a patch, then in the next major version returned `IDisposable` and made the lease a private nested class. I enabled CA1034 and the public API analyser. **Result:** later changes to the lease implementation (including moving to the .NET 9 `Lock` type) needed no consumer changes.

↩ Usually asked after

More in C# OOP Features

Last updated 30 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me