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

📚 Interview question

What is the difference between a class and an object in C#? Explain how an object is created with new, where it lives, what instance and static members belong to, and how object identity differs from object equality.

Classes as blueprints and objects as runtime instances: creation, memory, instance versus static members, identity and lifetime.

Junior Theory Beginner class instance-members object oop static-members

🎯 Key points

  • A class is a type definition; an object is a runtime instance created with `new`
  • `new` allocates on the managed heap, runs the constructor and returns a reference
  • Each object has its own instance fields; static members are shared by the class
  • Assigning a class variable copies the reference, not the object
  • Identity (`ReferenceEquals`) differs from equality (`Equals`), which classes default to identity for
  • Objects live until unreachable, then the GC reclaims them; there is no delete
  • Deterministic resource clean-up uses `IDisposable` and `using`
  • Static state is shared across threads and users, so avoid per-request data in it

⏱ 30-second answer

A class is a type definition: a blueprint that declares fields (state), methods (behaviour), properties, events and constructors. An object is one instance of that class created at run time with new, which allocates it on the managed heap, runs the constructor, and gives you a reference to it. Each object has its own copy of the instance fields; static members belong to the class itself and are shared by all objects. Two references can point to the same object (identity, ReferenceEquals), or to different objects that hold the same values (equality, which depends on how Equals is defined). An object lives until nothing references it and the garbage collector reclaims it.

🗣 Full answer (senior)

How I would answer this in an interview

A class is a type definition — a blueprint describing data (fields, properties) and behaviour (methods). An object is an instance of that class created at run time, with its own copy of the instance state. Then I cover how new creates it, where it lives, instance vs static members, and identity vs equality.

1. Class vs object

public sealed class Order(Guid id)          // the class: compiled once into metadata + IL
{
    private static int s_created;           // static: ONE per type
    private readonly List<OrderLine> _lines = [];   // instance: one per object

    public Guid Id { get; } = id;
    public Order() : this(Guid.NewGuid()) => Interlocked.Increment(ref s_created);
    public static int Created => s_created;
}

var a = new Order();   // object #1
var b = new Order();   // object #2 - same class, separate state

The class exists once (its metadata and a MethodTable in the runtime); you can create any number of objects from it.

2. What new does

  1. The runtime allocates memory on the managed heap — usually a fast pointer bump in the thread's allocation context; objects ≥ 85,000 bytes go to the Large Object Heap.
  2. The memory is zeroed, so all fields start at their defaults.
  3. The object header and MethodTable pointer (the object's type) are set.
  4. Field initialisers and the constructor chain run (base first, then derived).
  5. new returns a reference to the object, which you store in a variable.

3. Where it lives

  • Class instances live on the managed heap; the variable holds a reference (8 bytes on 64-bit), which may be on the stack, in a register, or inside another object.
  • The object lives as long as it is reachable from a GC root; the garbage collector reclaims it later — there's no delete.
  • (Modern JITs can stack-allocate small objects that provably never escape a method, but semantically you should think "heap".)
  • Structs differ: a struct value lives inline wherever its variable lives.

4. Instance vs static members

Instance members Static members
Belong to Each object The type itself
Accessed via order.Total Order.Created
Has this Yes No
Lifetime The object's lifetime The app's lifetime (per type, per closed generic)
Typical use State and behaviour of one thing Factories, constants, caches, helpers

Static mutable state is shared by every request and thread — a common source of race conditions and test pollution. In ASP.NET Core, shared state belongs in DI singletons.

5. Identity vs equality

  • Identity: are these the same object? ReferenceEquals(a, b). Two new calls always produce different identities.
  • Equality: do they represent the same value? Equals/==. For classes the default is reference equality; types such as string and records override it for value equality.
var m1 = new Money(10m, "AUD");   // record
var m2 = new Money(10m, "AUD");
ReferenceEquals(m1, m2);          // false - two objects
m1 == m2;                         // true  - same value

Entities (orders, customers) usually have identity defined by an ID; value objects (money, addresses) have value equality.

6. A production example

A caching bug in an API stemmed from treating identity as equality: a Dictionary<Customer, Discount> used Customer objects loaded in different requests as keys. Two objects for the same customer had different identities (no Equals override), so the cache never hit and grew without bound. Keying by CustomerId (a value) fixed both the hit rate and the memory growth.

🔍 What the interviewer is checking

A fundamentals check that also reveals whether the candidate understands memory and state.

  • Green flags: clear blueprint versus instance answer, mentions heap allocation and references, instance versus static, identity versus equality, GC lifetime.
  • Red flags: cannot explain static members, believes assignment copies objects, or thinks objects are destroyed at end of scope.

📚 Learn in depth

1. The class at run time: metadata and MethodTable

When the compiler builds a class it emits metadata (TypeDef, fields, methods, signatures) and IL. At run time, the first use of the type makes the CLR build:

  • a MethodTable: type identity, base type pointer, interface map, vtable (slots for virtual methods), base size of instances, and GC info describing which fields are references;
  • an EEClass: less frequently used data (field descriptors, method descriptors);
  • static storage for static fields, initialised by the type's static constructor.

Every object points to its MethodTable; that is how GetType(), casts, virtual calls and the GC know what the object is.

2. Object layout (64-bit)

[ object header (8 B) ][ MethodTable* (8 B) ][ instance fields ... ]
        ^ sync block index / hash code / thin lock      ^ reference points HERE
  • Minimum object size is 24 bytes.
  • The runtime may reorder fields of classes for alignment and packing (auto layout).
  • Reference fields are grouped so the GC can scan them efficiently.

You can inspect layouts with the ObjectLayoutInspector package or dotnet-dump (dumpobj).

3. Allocation fast path

Each thread has an allocation context — a small region of Gen0 memory. Allocating is: check that current + size <= limit, bump current, write the MethodTable pointer. That's a few instructions, which is why allocation in .NET is cheap; the real cost comes later as GC work proportional to surviving objects. When the context is exhausted, the allocator gets a new chunk, and may trigger a Gen0 GC.

4. Statics and generics

Static fields are per closed type: Cache<int>.Items and Cache<string>.Items are separate. Static fields of reference type are roots for the GC — anything referenced from a static lives forever unless removed, which is the classic static-cache memory leak. [ThreadStatic] and ThreadLocal<T> give per-thread statics; AsyncLocal<T> flows values with async context.

5. Identity mechanics

  • ReferenceEquals(a, b) compares the two pointers.
  • RuntimeHelpers.GetHashCode(obj) returns the identity hash (stored lazily in the object header), even if GetHashCode is overridden — used by ReferenceEqualityComparer (.NET 5) for identity-based dictionaries.
  • Because the GC moves objects during compaction, identity is not the memory address; it's the reference itself, which the GC updates.

6. Objects vs values in domain design

Concept Identity Equality C# typical choice
Entity ID, persists across changes By ID (or reference within a unit of work) class
Value object None All values record / readonly record struct
DTO None Rarely compared record
Service Single instance per lifetime N/A class registered in DI

Try it yourself

  1. Create two instances of a class and a record with the same data and compare ReferenceEquals, Equals and ==.
  2. Print RuntimeHelpers.GetHashCode for both objects.
  3. Use ObjectLayoutInspector to print the layout of a class with mixed field types.
  4. Add a static counter and observe it shared across all instances and threads.

💻 Code

public class BankAccount
{
    private static int _created;                 // shared by all accounts
    public static decimal InterestRate { get; } = 0.05m;

    private decimal _balance;                    // one per object

    public BankAccount(string owner)
    {
        Owner = owner;
        Id = ++_created;
    }

    public int Id { get; }
    public string Owner { get; }
    public decimal Balance => _balance;

    public void Deposit(decimal amount)
    {
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
        _balance += amount;                      // changes only this object
    }

    public static int CreatedCount => _created;  // no 'this' in static members
}

var alice = new BankAccount("Alice");
var bob   = new BankAccount("Bob");
alice.Deposit(100m);

Console.WriteLine(alice.Balance);              // 100
Console.WriteLine(bob.Balance);                // 0
Console.WriteLine(BankAccount.CreatedCount);   // 2

var sameAsAlice = alice;
sameAsAlice.Deposit(50m);
Console.WriteLine(alice.Balance);              // 150: same object
Console.WriteLine(ReferenceEquals(alice, sameAsAlice)); // true

⚠ Common mistakes

1. Saying "class and object are the same thing"

What happens: candidates use the terms interchangeably. Why it is wrong: the class is the definition; objects are runtime instances with their own state. Do instead: describe the class as a type/blueprint and objects as instances created with new.

2. Assuming assignment copies an object

What happens: var copy = order; copy.Status = Cancelled; expecting order unchanged. Why it is wrong: both variables reference the same object. Do instead: clone explicitly or use records with with for non-destructive changes.

3. Confusing identity with equality

What happens: comparing objects with == expecting value comparison, or using entity objects as dictionary keys. Why it is wrong: default class equality is reference equality. Do instead: compare IDs for entities, use records for values, or implement equality deliberately.

4. Mutable static state in web apps

What happens: static List<Order> Pending shared across requests. Why it is wrong: race conditions, cross-tenant data leaks, and tests that interfere with each other. Do instead: use DI singletons with thread-safe structures, or scoped services.

5. Static caches that never shrink

What happens: static dictionaries keep adding entries. Why it is wrong: statics are GC roots — memory grows until the process is killed. Do instead: use IMemoryCache with size limits and expiry.

6. Believing objects are freed when they go out of scope

What happens: expecting a file or connection to close when the variable goes out of scope. Why it is wrong: the GC reclaims memory later, non-deterministically; unmanaged resources need Dispose. Do instead: use using/await using for disposable objects.

7. Overusing static helpers instead of objects

What happens: business logic in static classes with parameters passed everywhere. Why it is wrong: hard to substitute in tests and hides dependencies. Do instead: use instance classes with injected dependencies for behaviour that depends on configuration or I/O.

8. Saying "objects always live on the heap and structs on the stack"

What happens: oversimplified memory explanations. Why it is wrong: structs live inline in their container (often the heap), and the JIT can stack-allocate non-escaping objects. Do instead: explain semantics first (reference vs value), then location as an implementation detail.

🏛 Senior insight: trade-offs

Seniors use the class/object distinction to reason about state, sharing and lifetime, which is where most real bugs come from.

Question Why it matters
Where does this state live: object, static, or outside? shared state causes data leaks and races
Who holds references to this object? lifetime, memory leaks, unintended mutation
Identity or value semantics? equality, dictionary keys, caching
Is the object always valid after construction? invariants, fewer defensive checks
Does it own resources? needs IDisposable

They also connect this to dependency injection lifetimes: a singleton service is effectively one shared object, so it must not hold per-request state.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate says new calls the constructor Why: Checks they know construction order, chaining and initialisation
    How to answer

    A constructor runs when an object is created and should leave it valid. If you declare none, the compiler adds a public parameterless one. Constructors can chain: : this(...) calls another constructor of the same class, : base(...) calls the base class's. For new Derived(), the order is: derived field initialisers, base field initialisers, base constructor body, derived constructor body. A static constructor runs once, before the type is first used, and is thread-safe. Primary constructors (C# 12) put parameters on the class declaration; they are captured as hidden fields, not properties, and are mutable. required members (C# 11) force callers to set them in an object initialiser. Calling a virtual method from a constructor runs the derived override before the derived constructor body has run, so it can see uninitialised state.

  2. When: Candidate says objects live on the heap Why: Probes value versus reference type semantics
    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.

  3. When: Candidate mentions identity versus equality Why: Checks they can override Equals and GetHashCode correctly
    How to answer

    System.Object gives every type Equals, GetHashCode, ToString, GetType, the protected MemberwiseClone and Finalize, plus static ReferenceEquals. The review problem is the Equals/GetHashCode contract: equal objects must return equal hash codes. If Equals compares Email but GetHashCode is still identity-based, two "equal" customers land in different hash buckets, so Dictionary, HashSet, Distinct and GroupBy treat them as different. I'd implement IEquatable<Customer>, override both methods using the same immutable fields, compare emails case-insensitively in both, overload ==/!=, and seal the class. For value-like data a record generates all of this.

More in Core OOP

Last updated 30 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me