Mock Interviews


Mock Interviews Interview with follow-up questions

1. Can you explain the concept of polymorphism in C#?

Polymorphism ("many forms") allows a single interface or base type to work with different underlying types, enabling flexible and extensible code.

Runtime polymorphism — the classic example:

public abstract class Shape
{
    public abstract double Area();
    public override string ToString() => $"{GetType().Name}: area = {Area():F2}";
}

public class Circle(double radius) : Shape
{
    public override double Area() => Math.PI * radius * radius;
}

public class Rectangle(double width, double height) : Shape
{
    public override double Area() => width * height;
}

// Polymorphic usage
List shapes = [new Circle(5), new Rectangle(4, 6)];
foreach (var s in shapes)
    Console.WriteLine(s); // calls the correct Area() override at runtime

Compile-time polymorphism — method overloading:

public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
// Compiler selects the right overload based on argument types

override vs new — a classic interview trap:

class Base { public virtual void Print() => Console.WriteLine("Base"); }
class Derived : Base { public override void Print() => Console.WriteLine("Derived"); }
class Hidden : Base { public new void Print() => Console.WriteLine("Hidden"); }

Base b = new Derived();
b.Print(); // "Derived" — override: runtime type wins

Base h = new Hidden();
h.Print(); // "Base" — new: declared type wins (polymorphism broken!)

Modern alternative — pattern matching polymorphism:

double GetArea(Shape shape) => shape switch
{
    Circle c      => Math.PI * c.Radius * c.Radius,
    Rectangle r   => r.Width * r.Height,
    _             => throw new NotSupportedException()
};

Pattern matching is often cleaner than virtual dispatch when the behavior doesn't belong in the type hierarchy.

Interface polymorphism (most common in modern C#):

void RenderAll(IEnumerable renderers)
{
    foreach (var r in renderers) r.Render();
}

Any class implementing IRenderer is substitutable — no inheritance required.

Interviewers probe: what is the Liskov Substitution Principle? A derived class must be substitutable for its base class without breaking program correctness. Violations (e.g., an override that throws NotSupportedException) indicate a flawed hierarchy — composition is often the better design.

↑ Back to top

Follow-up 1

How does polymorphism enhance reusability in C#?

Polymorphism enhances reusability in C# by allowing you to write code that can work with objects of different classes, as long as they inherit from the same base class or implement the same interface. This means that you can write generic code that can be reused with different types of objects, without having to write separate code for each specific type. This promotes code reuse and reduces code duplication, making your code more maintainable and scalable.

Follow-up 2

Can you give an example of polymorphism in C#?

Sure! Here's an example of polymorphism in C#:

public abstract class Shape
{
    public abstract double CalculateArea();
}

public class Circle : Shape
{
    private double radius;

    public Circle(double radius)
    {
        this.radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * radius * radius;
    }
}

public class Rectangle : Shape
{
    private double width;
    private double height;

    public Rectangle(double width, double height)
    {
        this.width = width;
        this.height = height;
    }

    public override double CalculateArea()
    {
        return width * height;
    }
}

public class Program
{
    public static void Main()
    {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);

        Console.WriteLine("Area of circle: " + circle.CalculateArea());
        Console.WriteLine("Area of rectangle: " + rectangle.CalculateArea());
    }
}

In this example, the Shape class is an abstract base class with an abstract method CalculateArea(). The Circle and Rectangle classes inherit from the Shape class and override the CalculateArea() method. The Main() method demonstrates polymorphism by creating objects of type Circle and Rectangle, but treating them as objects of type Shape. This allows the code to calculate the area of different shapes using the same method call.

Follow-up 3

What is the difference between static and dynamic polymorphism in C#?

In C#, static polymorphism is achieved through method overloading, while dynamic polymorphism is achieved through method overriding.

Static polymorphism, also known as compile-time polymorphism, allows you to have multiple methods with the same name but different parameters in a class. The appropriate method to call is determined at compile-time based on the number and types of arguments passed to the method.

Dynamic polymorphism, also known as runtime polymorphism, allows you to have methods with the same signature in a base class and its derived classes. The appropriate method to call is determined at runtime based on the actual type of the object. This is achieved through method overriding, where a derived class provides its own implementation of a method defined in the base class.

Both static and dynamic polymorphism are important concepts in C# and provide different ways to achieve code reuse and flexibility in object-oriented programming.

2. What is the purpose of an interface in C#?

An interface in C# defines a contract — a named set of members (methods, properties, events, indexers) that any implementing type must provide. Interfaces are the primary abstraction mechanism in modern C# development.

Core purpose:

public interface INotificationSender
{
    Task SendAsync(string recipient, string message, CancellationToken ct = default);
}

public class EmailSender : INotificationSender
{
    public async Task SendAsync(string recipient, string message, CancellationToken ct)
        => await _smtpClient.SendMailAsync(recipient, message, ct);
}

public class SmsSender : INotificationSender
{
    public async Task SendAsync(string recipient, string message, CancellationToken ct)
        => await _smsGateway.SendAsync(recipient, message, ct);
}

// Code depends on the abstraction — works with any INotificationSender
public class AlertService(INotificationSender sender)
{
    public Task NotifyAsync(string user) =>
        sender.SendAsync(user, "Alert!", CancellationToken.None);
}

Why interfaces over abstract classes for DI targets: easily mocked in tests (Mock), supports multiple implementation, no inheritance coupling.

C# 8 — default implementations: add new members without breaking existing implementors:

public interface IFormatter
{
    string Format(string input);
    string FormatAll(IEnumerable inputs) =>
        string.Join(", ", inputs.Select(Format)); // default impl
}

C# 11 — static abstract members: enables generic algorithms:

interface IAddable { static abstract T Add(T a, T b); }
T Sum(T[] items) where T : IAddable =>
    items.Aggregate(T.Add);

Explicit interface implementation resolves naming conflicts when implementing multiple interfaces:

class MultiLogger : ILogger, IAuditor
{
    void ILogger.Log(string msg) { ... }
    void IAuditor.Log(string msg) { ... }
}

Interviewers probe: interface vs abstract class?

  • Interface: capability/CAN-DO; any unrelated types can share it; multiple per class.
  • Abstract class: IS-A; shared state + code; only one per class.

Prefer interfaces for DI targets. Use abstract classes when you genuinely need to share implementation among a closely related type hierarchy.

↑ Back to top

Follow-up 1

Can you give an example of when you would use an interface?

Sure! Let's say you have a program that needs to work with different types of shapes, such as circles, squares, and triangles. You can define an interface called IShape that declares methods like CalculateArea() and CalculatePerimeter(). Each shape class can then implement this interface and provide its own implementation for these methods. This allows you to write code that can work with any shape, regardless of its specific type.

Follow-up 2

What is the difference between an interface and an abstract class in C#?

In C#, an interface is a contract that defines a set of methods, properties, and events that a class must implement, while an abstract class is a class that cannot be instantiated and can contain both abstract and non-abstract members. The main differences between an interface and an abstract class are:

  • A class can implement multiple interfaces, but it can inherit from only one abstract class.
  • An interface cannot have any implementation, whereas an abstract class can have both abstract and non-abstract members.
  • An interface can be used to achieve multiple inheritance-like behavior, while an abstract class is used to provide a common base for related classes.

Follow-up 3

Can a class implement multiple interfaces in C#?

Yes, a class in C# can implement multiple interfaces. This allows the class to provide implementations for the members defined in each interface. To implement multiple interfaces, you can separate the interface names with commas in the class declaration, like this:

class MyClass : IInterface1, IInterface2
{
    // Implementations for IInterface1 and IInterface2
}

3. Can you explain the concept of multithreading in C#?

Multithreading enables a program to execute multiple operations concurrently. Modern C# provides a layered toolkit — choose the abstraction that fits:

Tier 1 — async/await for I/O-bound concurrency (preferred):

// Concurrent HTTP requests — no threads blocked while waiting
async Task FetchAllAsync(string[] urls, CancellationToken ct)
{
    var tasks = urls.Select(url => httpClient.GetStringAsync(url, ct));
    return await Task.WhenAll(tasks);
}

No threads are created; the thread pool thread is freed during the await and can handle other work.

Tier 2 — Task.Run() for CPU-bound work:

int result = await Task.Run(() => ComputeHeavy());

Tier 3 — Parallel.ForEach / Parallel.ForEachAsync for data parallelism:

// CPU-bound parallel processing
Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = 4 },
    item => Process(item));

// Async parallel (I/O-bound with concurrency limit)
await Parallel.ForEachAsync(items,
    new ParallelOptions { MaxDegreeOfParallelism = 10, CancellationToken = ct },
    async (item, token) => await ProcessAsync(item, token));

Channel — producer/consumer pipelines:

var channel = Channel.CreateBounded(capacity: 100);

// Producer
Task producer = Task.Run(async () =>
{
    foreach (var item in source)
        await channel.Writer.WriteAsync(item);
    channel.Writer.Complete();
});

// Consumer
await foreach (var item in channel.Reader.ReadAllAsync())
    await HandleAsync(item);

Synchronization:

Primitive Best for
lock / Lock (.NET 9) Short synchronous critical sections
SemaphoreSlim Async-compatible concurrency limiting
Interlocked.Increment() Atomic counters without locks
ConcurrentDictionary Thread-safe key-value operations

Raw Thread class — rarely needed. Use for STA COM threads or very specific OS-thread scenarios.

Interviewers probe: what's wrong with async void? Exceptions thrown inside an async void method propagate to the SynchronizationContext (not to the caller), making them uncatchable in normal try/catch. Only use async void for event handlers — everywhere else return Task.

↑ Back to top

Follow-up 1

What are the advantages of multithreading?

There are several advantages of multithreading in C#:

  1. Improved performance: Multithreading allows for parallel execution of tasks, which can significantly improve the performance of an application by utilizing the available CPU cores.

  2. Responsiveness: Multithreading can make an application more responsive by allowing time-consuming tasks to run in the background while the main thread remains responsive to user input.

  3. Concurrency: Multithreading enables multiple tasks to be executed concurrently, which can be useful for handling multiple requests or processing large amounts of data.

  4. Modularity: Multithreading allows for the separation of concerns by dividing a complex task into smaller, more manageable threads.

  5. Resource utilization: Multithreading can help maximize the utilization of system resources, such as CPU and memory, by efficiently distributing the workload among multiple threads.

Follow-up 2

What are the potential problems with multithreading and how can they be avoided?

Multithreading introduces several potential problems that need to be carefully managed:

  1. Race conditions: Race conditions occur when multiple threads access and modify shared data concurrently, leading to unpredictable and incorrect results. To avoid race conditions, synchronization mechanisms such as locks, mutexes, and semaphores can be used to ensure that only one thread can access the shared data at a time.

  2. Deadlocks: Deadlocks occur when two or more threads are waiting for each other to release resources, resulting in a deadlock state where none of the threads can proceed. Deadlocks can be avoided by carefully managing the order in which resources are acquired and released.

  3. Thread synchronization: Synchronizing access to shared resources can introduce performance overhead and potential bottlenecks. It is important to carefully design and optimize the synchronization mechanisms to minimize their impact on performance.

  4. Thread safety: Ensuring thread safety is crucial when working with multithreaded applications. This involves designing classes and methods in a way that they can be safely accessed and modified by multiple threads without causing data corruption or inconsistencies.

  5. Debugging and testing: Multithreaded applications can be more challenging to debug and test due to the non-deterministic nature of thread execution. Special care should be taken to identify and fix any issues related to thread synchronization, race conditions, and deadlocks.

Follow-up 3

Can you give an example of a situation where multithreading would be useful in C#?

Multithreading can be useful in various scenarios, such as:

  1. Parallel processing: When performing computationally intensive tasks, such as image or video processing, multithreading can be used to divide the workload among multiple threads, allowing for faster execution.

  2. Asynchronous operations: Multithreading can be used to perform time-consuming operations, such as network requests or database queries, in the background while keeping the main thread responsive to user input.

  3. Concurrency control: In scenarios where multiple users or processes need to access and modify shared resources, multithreading can be used to handle concurrent requests and ensure data consistency.

  4. User interface responsiveness: Multithreading can be used to offload time-consuming tasks, such as data loading or complex calculations, from the main UI thread, ensuring that the user interface remains responsive and smooth.

Here is an example of using multithreading for parallel processing in C#:

using System;
using System.Threading;

public class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        // Create an array to store the results
        int[] results = new int[numbers.Length];

        // Create and start multiple threads to process the numbers
        for (int i = 0; i < numbers.Length; i++)
        {
            int index = i; // Capture the current index
            Thread thread = new Thread(() =>
            {
                results[index] = ProcessNumber(numbers[index]);
            });
            thread.Start();
        }

        // Wait for all threads to complete
        foreach (Thread thread in threads)
        {
            thread.Join();
        }

        // Print the results
        foreach (int result in results)
        {
            Console.WriteLine(result);
        }
    }

    static int ProcessNumber(int number)
    {
        // Code to process the number
        return number * 2;
    }
}

4. What is LINQ in C# and why is it useful?

LINQ (Language Integrated Query) adds first-class query syntax to C# for any data source that implements IEnumerable (in-memory) or IQueryable (translated to SQL or another query language).

Two equivalent syntax styles:

int[] scores = [72, 85, 91, 60, 88, 77];

// Query syntax
var highScores = from s in scores
                 where s >= 80
                 orderby s descending
                 select s;

// Method (fluent) syntax — more common in real codebases
var highScores2 = scores
    .Where(s => s >= 80)
    .OrderByDescending(s => s);

Why LINQ is useful:

  • Unified — same API for arrays, List, Dictionary, EF Core database queries, XML, and more
  • Type-safe — compile-time checking and IntelliSense
  • Composable — chain operators to build readable pipelines
  • Less code — replaces many manual for/foreach loops

Deferred vs immediate execution:

var query = scores.Where(s => s >= 80); // deferred — no work done yet
Console.WriteLine(query.Count());        // executes now
Console.WriteLine(query.Count());        // executes again — iterates source twice

var cached = query.ToList();             // materialize once, iterate many times

Key operators to know:

  • Filtering: Where, OfType
  • Projection: Select, SelectMany (flatten)
  • Aggregation: Count, Sum, Average, Min, Max, Aggregate
  • Element: First, FirstOrDefault, Single, SingleOrDefault, Any, All
  • Set: Distinct, Union, Intersect, Except
  • Grouping/joining: GroupBy, Join, GroupJoin
  • Partitioning: Take, Skip, Chunk (.NET 6+)

.NET 6+ additions: MinBy(), MaxBy(), DistinctBy(), ExceptBy(), IntersectBy(), UnionBy(), Chunk():

var youngest = people.MinBy(p => p.Age);
var chunks = scores.Chunk(3); // [[72,85,91],[60,88,77]]

EF Core LINQ: IQueryable queries are translated to SQL. AsNoTracking() for read-only, avoid client-side evaluation of untranslatable expressions.

Interviewers probe: Any() vs Count() > 0Any() short-circuits on the first match and is O(1) for indexed sources; Count() may enumerate the entire collection. Always prefer Any() for existence checks.

↑ Back to top

Follow-up 1

Can you give an example of a situation where you would use LINQ?

Sure! Let's say you have a list of employees and you want to find all the employees who have a salary greater than a certain threshold. You can use LINQ to easily filter the list based on the salary criteria. Here's an example:

List employees = GetEmployees();

var highPaidEmployees = employees.Where(e => e.Salary > 5000);

foreach (var employee in highPaidEmployees)
{
    Console.WriteLine(employee.Name);
}

In this example, the Where operator is used to filter the employees based on the salary condition, and the resulting sequence of employees is then printed to the console.

Follow-up 2

What are the different types of LINQ operations in C#?

There are several types of LINQ operations in C#:

  1. Query Operators: These are the standard query operators provided by LINQ, such as Where, Select, OrderBy, GroupBy, etc. These operators are used to perform various operations on data.

  2. Join Operations: LINQ provides join operations like Join, GroupJoin, Zip, etc., which allow you to combine data from multiple sources based on specified conditions.

  3. Set Operations: LINQ provides set operations like Union, Intersect, Except, etc., which allow you to perform operations on sets of data.

  4. Aggregate Operations: LINQ provides aggregate operations like Count, Sum, Average, Min, Max, etc., which allow you to perform calculations on data.

These are just a few examples, and there are many more LINQ operations available in C#.

Follow-up 3

How does LINQ improve code readability and maintainability in C#?

LINQ improves code readability and maintainability in C# in several ways:

  1. Unified Syntax: LINQ provides a unified syntax for querying and manipulating data from different sources. This makes the code more readable and easier to understand, as you don't have to switch between different query languages or APIs.

  2. Intuitive Expressions: LINQ uses lambda expressions and query expressions, which are concise and expressive. This allows you to write queries in a natural and intuitive way, making the code easier to read and maintain.

  3. Type Safety: LINQ is strongly typed, which means that the compiler can catch type-related errors at compile-time. This helps in reducing runtime errors and makes the code more robust and maintainable.

  4. Code Reusability: LINQ queries can be easily reused and composed with other queries. This promotes code reusability and reduces code duplication, leading to more maintainable code.

Overall, LINQ simplifies the process of querying and manipulating data, resulting in code that is more readable, maintainable, and less error-prone.

5. What is the .NET Framework and how does it relate to C#?

The honest 2025-2026 answer: ".NET Framework" is the original, legacy platform. The current platform for C# development is simply ".NET" (currently .NET 9). Confusing these two is a common mistake interviewers watch for.

The relationship:

C# is the language; .NET is the platform. C# source compiles to CIL (Common Intermediate Language) bytecode. The .NET runtime (CLR) JIT-compiles CIL to native code at runtime and provides garbage collection, type safety, and the Base Class Library.

The platform evolution:

Platform Years Notes
.NET Framework (1.0–4.8.1) 2002–present Windows-only; legacy, maintenance only
.NET Core (1.0–3.1) 2016–2019 Cross-platform reboot
.NET 5–9+ 2020–present Unified, current platform

".NET Core" was the brand for the cross-platform rewrite (versions 1.0–3.1). Starting with .NET 5 in 2020, the "Core" suffix was dropped — it's now just ".NET". The current version is .NET 9 (released November 2024 with C# 13).

What .NET Framework and modern .NET share: C# as the primary language, the CLR execution model, and the System.* BCL foundation.

What differentiates them:

Modern .NET (9) .NET Framework (4.8)
Cross-platform Windows only
Open source Partially open
Native AOT support None
Significantly faster Slower
In active development Maintenance only

C# version tracks .NET release: C# 13 → .NET 9; C# 12 → .NET 8; C# 11 → .NET 7. You can't use C# 13 features on .NET Framework (it caps at C# 7.3 for the latest toolchain).

Interviewers probe: "Should I target .NET Framework for a new project?" — No. Target .NET 8 (LTS) or .NET 9. The only reason to choose .NET Framework is maintaining an existing legacy application that can't be migrated.

↑ Back to top

Follow-up 1

What are the main components of the .NET Framework?

The main components of the .NET Framework include:

  1. Common Language Runtime (CLR): The CLR is the execution engine of the .NET Framework. It provides services such as memory management, exception handling, and security.

  2. Base Class Library (BCL): The BCL is a collection of classes, interfaces, and value types that provide common functionality for .NET applications. It includes classes for working with strings, collections, file I/O, networking, and more.

  3. Framework Class Library (FCL): The FCL is a set of libraries that provide additional functionality for specific application types, such as Windows Forms, ASP.NET, and WPF.

  4. Language Compilers: The .NET Framework includes compilers for different programming languages, such as C#, Visual Basic.NET, and F#. These compilers translate the source code written in these languages into an intermediate language called Common Intermediate Language (CIL), which can be executed by the CLR.

  5. Development Tools: The .NET Framework includes development tools such as Visual Studio, which provide an integrated development environment (IDE) for building .NET applications.

Follow-up 2

What is the difference between .NET Framework and .NET Core?

The .NET Framework and .NET Core are both software development frameworks developed by Microsoft, but they have some key differences:

  1. Platform Support: The .NET Framework is primarily designed for Windows-based applications, while .NET Core is designed to be cross-platform and can run on Windows, macOS, and Linux.

  2. Deployment Model: The .NET Framework requires the installation of the full framework on the target machine, while .NET Core can be deployed as a self-contained application that includes all the necessary dependencies.

  3. Size and Performance: .NET Core is generally smaller and faster than the .NET Framework, as it has been optimized for modern application development scenarios.

  4. Compatibility: The .NET Framework has a larger ecosystem and supports a wider range of libraries and frameworks, while .NET Core has a smaller ecosystem but is growing rapidly.

  5. Long-Term Support: The .NET Framework has long-term support from Microsoft, while .NET Core follows a more frequent release cycle and has a shorter support lifecycle.

Follow-up 3

How does the .NET Framework support the development of C# applications?

The .NET Framework provides a runtime environment and a set of libraries that enable the development of C# applications. Here are some ways in which the .NET Framework supports C# development:

  1. Common Language Runtime (CLR): The CLR is responsible for executing C# code. It provides services such as memory management, exception handling, and security.

  2. Base Class Library (BCL): The BCL provides a wide range of classes and APIs that C# developers can use to build applications. It includes classes for working with strings, collections, file I/O, networking, and more.

  3. Language Integration: The .NET Framework provides support for multiple programming languages, including C#. This means that C# can interoperate with other .NET languages, allowing developers to leverage code written in different languages.

  4. Development Tools: The .NET Framework includes development tools such as Visual Studio, which provide an integrated development environment (IDE) for building C# applications. These tools offer features like code editing, debugging, and project management.

Overall, the .NET Framework provides a robust and comprehensive platform for developing C# applications, making it easier for developers to write, test, and deploy their code.

Live mock interview

Mock interview: Mock Interviews

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.