The C# Fundamentals Every Interviewer Asks

1 min read

Your Guide to C# and .NET Core Fundamentals

In any C# interview, the interviewer's first goal is to verify your foundational knowledge. They want to know that you don't just use C#, but that you understand how it works under the hood.

This means understanding memory, Object-Oriented Programming (OOP) principles, and the core building blocks of the language.

The questions in this area are designed to test your understanding of 'why' you do things, not just 'how'.

The 'Must-Know' Core Concepts:

  • Memory: What is the Stack and the Heap? (class vs. struct)
  • OOP Pillars: What's the difference between an interface and an abstract class?
  • OOP Design: Can you explain the SOLID principles?
  • Language Keywords: What is the difference between readonly and const?

If you can answer these questions confidently, you'll establish a strong foundation for the rest of the interview. This cluster will break down each of these key topics.

Stack vs. Heap: A Deep Dive into C# Memory

Interview Question: 'Can you explain the Stack and the Heap?'

This is a fundamental .NET question. Your answer demonstrates your understanding of how .NET manages memory, which is critical for performance.

The simple answer: 'The Stack and the Heap are the two places .NET stores data in memory. The Stack is a fast, simple 'LIFO' (Last-In, First-Out) data structure for static memory allocation. It stores value types (like int, bool, structs) and pointers. The Heap is a larger, more complex area for dynamic memory allocation. It stores reference types (like class objects, strings, and arrays).'


1. The Stack (Fast & Simple)

  • What it is: A small, highly efficient block of memory.
  • How it works: When a method is called, a 'stack frame' is pushed onto the top of the stack. This frame holds all the local variables for that method. When the method finishes, its frame is 'popped' off the stack, and all its memory is instantly freed.
  • Stored here: Value Types (int, double, bool, char, structs, enums) and pointers/references.
  • Analogy: A stack of plates. You can only add or remove from the top. It's very fast.

2. The Heap (Large & Dynamic)

  • What it is: A large, unstructured region of memory.
  • How it works: When you create an object (e.g., new MyClass()), .NET finds a free block of memory in the Heap to store it. It then places a pointer (the memory address) to that object on the Stack.
  • Stored here: Reference Types (class instances, string, object, arrays, delegates).
  • Cleanup: Memory on the Heap is not freed when a method ends. It is cleaned up by the Garbage Collector (GC), which periodically checks for objects on the Heap that no longer have any references pointing to them.

Code Example: Putting it Together

public class MyPerson { 
  public string Name { get; set; } // 'Name' (string) will be on the Heap
}

public void DoWork() {
  // 1. 'age' is an 'int' (Value Type).
  // It is stored directly on the Stack.
  int age = 30;

  // 2. 'personObject' is a 'MyPerson' (Reference Type).
  // The object data (with its 'Name' property) is created on the Heap.
  // The pointer 'personObject' is stored on the Stack.
  MyPerson personObject = new MyPerson() { Name = "Alice" };

  // 3. 'anotherAge' is a Value Type. 'age' is copied.
  int anotherAge = age;
  anotherAge = 31; // 'age' is still 30.

  // 4. 'anotherPerson' is a Reference Type. The pointer is copied.
  MyPerson anotherPerson = personObject;
  anotherPerson.Name = "Bob"; // This changes the same object on the Heap.
  // 'personObject.Name' is now also 'Bob'.

} // When DoWork() ends, its stack frame (age, personObject, anotherAge, anotherPerson) is popped.
  // The 'MyPerson' object on the Heap is now orphaned and will be cleaned up by the GC.

Interface vs. Abstract Class: Which to Choose?

Interview Question: 'When would you use an interface vs. an abstract class?'

This is a classic OOP design question. Your answer should focus on 'has-a' vs. 'is-a' relationships and the intent behind your design.

The simple answer: 'An abstract class defines what an object is (an 'is-a' relationship) and can provide shared implementation. A class can only inherit from one. An interface defines what an object can do (a 'has-a' or 'can-do' capability) and provides no implementation (in classic C#). A class can implement many interfaces.'

This table summarizes the key differences:

| Feature | Abstract Class | Interface | |---|---|---| | Inheritance | A class can inherit from only one. | A class can implement many. | | Implementation | Can provide method implementations. | Traditionally, cannot (pre-C# 8). | | Fields/State | Can have instance fields. | Cannot have instance fields. | | Access Modifiers | Can use public, protected, private. | All members are implicitly public. | | Purpose | To share common code among related classes. (e.g., Animal) | To define a 'contract' for a capability. (e.g., IWalkable, ISwimmable) |

Example: Abstract Class (The 'is-a' Relationship)

Use an abstract class when you have a base class that needs to share code. A Dog 'is-a' Animal. A Cat 'is-a' Animal. They share a common Name property and Eat() method.

// Defines the identity of an Animal
public abstract class Animal {
  // Can have fields and implemented properties
  public string Name { get; set; }

  // Can have a fully implemented method
  public void Eat() {
    Console.WriteLine($"{Name} is eating.");
  }

  // Can also have an abstract method that must be overridden
  public abstract void MakeSound();
}

// A Dog 'is-a' Animal
public class Dog : Animal {
  public override void MakeSound() {
    Console.WriteLine("Woof!");
  }
}

Example: Interface (The 'can-do' Capability)

Use an interface to define a capability that can be applied to many different types of classes. A Dog 'can-do' IWalkable. A Person 'can-do' IWalkable. A Robot 'can-do' IWalkable. These classes are not related, but they share a capability.

// Defines the capability of walking
public interface IWalkable {
  // Just the contract, no implementation
  void Walk();
  int NumberOfLegs { get; }
}

// A Dog implements IWalkable
public class Dog : Animal, IWalkable {
  public int NumberOfLegs { get; } = 4;

  public void Walk() {
    Console.WriteLine("Dog is walking on 4 legs.");
  }
  
  public override void MakeSound() {
    Console.WriteLine("Woof!");
  }
}

// A Robot is not an Animal, but it also implements IWalkable
public class Robot : IWalkable {
  public int NumberOfLegs { get; } = 2;

  public void Walk() {
    Console.WriteLine("Robot is rolling on 2 wheels.");
  }
}

Final Answer: 'I use an abstract class to create a base for closely related objects. I use an interface to define a contract for a capability that can be shared by unrelated classes. If I can, I prefer to use an interface because it's more flexible.'

Understanding SOLID Principles with Code Examples

Interview Question: 'Can you explain the SOLID principles?'

This is a senior-level OOP design question. Explaining it well shows you care about writing clean, maintainable, and testable code. Just listing the names is not enough; you must explain what they mean.

Answer: 'SOLID is an acronym for five design principles that help create more understandable and flexible software.'


S - Single Responsibility Principle (SRP)

  • Concept: A class should have only one reason to change.
  • Meaning: A class should do one job, and do it well. Don't create a 'God Class' that handles user logic, database saving, and email sending.
  • Bad Example: A UserService class that also writes to a log file.
  • Good Example: A UserService that handles user logic, and an ILoggerService that is injected into it to handle logging. If the logging logic changes (e.g., log to cloud instead of file), UserService does not need to change.

O - Open/Closed Principle (OCP)

  • Concept: Software entities (classes, modules) should be open for extension, but closed for modification.
  • Meaning: You should be able to add new functionality without changing existing, tested code. This is often achieved with interfaces or abstract classes.
  • Bad Example: A CalculateBonus method with a giant if/else or switch statement for each employee type. To add a new employee type, you must modify this method.
  • Good Example: An IEmployee interface with a CalculateBonus() method. Each employee class (Manager, Developer) implements it. To add a Contractor, you just create a new class; no existing code changes.
// OCP Example
public interface IEmployee {
  decimal CalculateBonus();
}
public class Developer : IEmployee {
  public decimal CalculateBonus() => 1000;
}
public class Manager : IEmployee {
  public decimal CalculateBonus() => 2000;
}
// To add a new type, just add a new class. The calculation logic is 'open' to extension.

L - Liskov Substitution Principle (LSP)

  • Concept: Subtypes must be substitutable for their base types.
  • Meaning: A child class should be able to do everything its parent class can do, without breaking things. If you have a method that takes an Animal, you should be able to pass it a Dog (a child of Animal) and have it work perfectly.
  • Bad Example: A Bird base class with a Fly() method. A Penguin child class inherits from Bird, but it can't fly. If you override Fly() to throw an exception, you've violated LSP.
  • Good Example: A Bird base class without a Fly() method. You would have a separate IFlyable interface that Sparrow implements, but Penguin does not.

I - Interface Segregation Principle (ISP)

  • Concept: Clients should not be forced to depend on methods they do not use.
  • Meaning: Make your interfaces small and specific. Don't create one giant 'fat' interface.
  • Bad Example: One IWorker interface with Work() and EatLunch() methods. A RobotWorker class is forced to implement EatLunch(), which it doesn't do (it would throw an exception).
  • Good Example: Two separate, small interfaces: IWorkable and IEatable. HumanWorker implements both. RobotWorker only implements IWorkable.

D - Dependency Inversion Principle (DIP)

  • Concept: High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).
  • Meaning: Don't 'new up' your dependencies. Instead, 'ask' for them in your constructor (Dependency Injection). This decouples your code.
  • Bad Example: A NotificationService (high-level) creates a new instance of EmailSender (low-level). NotificationService is now hard-coded to EmailSender. You can't test it without sending a real email.
  • Good Example: NotificationService depends on an IMessageSender interface. You inject an EmailSender class in production. In a unit test, you can inject a MockMessageSender. The high-level service is 'inverted'—it no longer controls its dependencies.
// DIP Example
public class NotificationService {
  private readonly IMessageSender _sender;

  // Depends on an abstraction (interface), not a concretion (class)
  public NotificationService(IMessageSender sender) {
    _sender = sender;
  }

  public void Notify(string message) {
    _sender.SendMessage(message);
  }
}

C# Fundamentals: struct vs. class Explained

Interview Question: 'What's the difference between a class and a struct?'

This is a C# fundamentals question that will be asked. It tests your understanding of value types vs. reference types and memory allocation.

Key Interview Answer Points:

The shortest, most accurate answer has two parts:

  1. A class is a reference type. A struct is a value type.
  2. Class instances are stored on the Heap. Struct instances are typically stored on the Stack (if they are local variables).

This difference in memory has huge implications for performance and behavior.


Code Example: The Assignment Difference

This is the most important concept to show. When you assign a class, you copy the reference. When you assign a struct, you copy the value.

// 1. The Class (Reference Type)
public class MyPointClass {
  public int X { get; set; }
}

// 2. The Struct (Value Type)
public struct MyPointStruct {
  public int X { get; set; }
}

public void Demonstrate() {
  // --- Class Example (Copying a Reference) ---
  MyPointClass classA = new MyPointClass() { X = 10 };
  MyPointClass classB = classA; // 'classB' now points to the same object as 'classA'

  classB.X = 20;

  Console.WriteLine(classA.X); // Output: 20


  // --- Struct Example (Copying a Value) ---
  MyPointStruct structA = new MyPointStruct() { X = 10 };
  MyPointStruct structB = structA; // 'structB' is a brand new copy of 'structA'

  structB.X = 20;

  Console.WriteLine(structA.X); // Output: 10
}

When should you use a struct?

The interviewer might ask for a follow-up. 'When would you actually use one?'

Answer: 'You should default to using a class. You should only use a struct for small, lightweight objects that logically represent a single value, have no complex behavior, and are immutable.'

Good examples for struct:

  • Point (with X and Y)
  • Color (with R, G, B)
  • KeyValuePair

Using a struct in these cases can be a performance win because you avoid allocating memory on the Heap and putting pressure on the Garbage Collector.

💬