Design Patterns & Practices  

System Design Interview Question

Why is DbContext Scoped?

DbContext is typically registered as Scoped because it is designed to handle a single unit of work, usually one HTTP request.

builder.Services.AddDbContext<AppDbContext>();

By default, AddDbContext() registers it as Scoped.

Why Scoped?

For each request:

  • A new DbContext is created.

  • All repositories/services in that request share the same context.

  • Changes are tracked consistently.

  • The context is disposed when the request ends.

Problems with Singleton DbContext

services.AddSingleton<AppDbContext>();

Bad because:

  • DbContext is not thread-safe.

  • Multiple requests may use the same instance simultaneously.

  • Change Tracking grows indefinitely.

  • Memory leaks can occur.

  • Data inconsistencies become likely.

Problems with Transient DbContext

services.AddTransient<AppDbContext>();

Each injection gets a new instance.

RepositoryA -> DbContext A
RepositoryB -> DbContext B

A single business transaction may use multiple contexts, making transactions and tracking difficult.

InShort

DbContext is registered as Scoped because one database context should generally serve one request or unit of work. This ensures proper change tracking, transaction management, and thread safety while avoiding the problems associated with Singleton or excessive context creation from Transient lifetimes.

Why shouldn't Scoped be injected into Singleton?

Example

services.AddScoped<IUserService, UserService>();
services.AddSingleton<ReportGenerator>();
public class ReportGenerator
{
    public ReportGenerator(IUserService userService)
    {
    }
}

This causes a lifetime mismatch.

Why?

A Singleton is created once:

Application Start
    |
    +-- Singleton Created

A Scoped service exists per request:

Request 1 → UserService Instance A
Request 2 → UserService Instance B

The Singleton would hold a reference to the first scoped instance forever.

Problems

  • Stale data.

  • Memory leaks.

  • Unexpected behavior.

  • Runtime DI exceptions.

ASP.NET Core often throws:

Cannot consume scoped service from singleton

Correct Approach

Inject:

  • IServiceProvider

or

  • IServiceScopeFactory

and create a scope when needed.

InShort

A Scoped service has a shorter lifetime than a Singleton. Injecting a Scoped dependency into a Singleton can cause stale references, thread-safety issues, and runtime errors because the Singleton may outlive the Scoped object.

Why can caching improve performance AND create bugs?

Caching stores frequently accessed data in memory so it can be reused instead of repeatedly fetching it from a database or external service.

Performance Improvement

Without cache:

Request
  ↓
Database
  ↓
Response

Every request hits the database.

With cache:

Request
  ↓
Cache
  ↓
Response

Benefits:

  • Faster response times.

  • Reduced database load.

  • Lower network latency.

  • Better scalability.

Example:

_memoryCache.GetOrCreate(
    "Products",
    entry =>
    {
        return repository.GetProducts();
    });

How Bugs Happen

Stale Data

Database:

Product Price = 100

Cache:

Product Price = 100

Database updated:

Product Price = 120

Cache still returns:

100

Users see incorrect data.

Cache Invalidation Problems

One of the hardest problems in software:

When should cache be refreshed?

Too long:

  • Users see old data.

Too short:

  • Cache becomes ineffective.

Distributed Cache Issues

In multiple servers:

Server A Cache = Updated
Server B Cache = Old

Users may receive inconsistent responses.

InShort

Caching improves performance by reducing expensive operations such as database queries and API calls. However, it can introduce bugs through stale data, invalidation issues, synchronization problems, and inconsistent state across multiple servers.

Why does an API become slow in production?

An API that is fast locally may become slow under real-world load.

1. Database Bottlenecks

Most common reason.

var orders = _context.Orders.ToList();

Poor indexing or inefficient queries can dramatically increase response times.

2. N+1 Query Problem

foreach(var order in orders)
{
    var customer = order.Customer;
}

One query becomes hundreds of queries.

3. External Service Calls

  • Payment API

  • Email API

  • Third-party API

Even if your API is fast, waiting on external systems slows everything down.

4. Excessive Logging

_logger.LogInformation(...);

Huge amounts of logging can impact performance.

5. Thread Pool Starvation

Blocking calls:

  • Thread.Sleep()

  • .Result

  • .Wait()

consume worker threads and reduce throughput.

6. Memory Pressure

Large objects:

List<Customer> customers = GetMillions();

cause:

  • High GC activity.

  • Increased CPU usage.

  • Slower response times.

7. Network Latency

Production systems involve:

Client
 ↓
Load Balancer
 ↓
API
 ↓
Database

Each hop adds latency.

InShort

APIs usually become slow in production due to database bottlenecks, N+1 queries, external service dependencies, excessive logging, memory pressure, thread pool starvation, high traffic, or network latency. Profiling and monitoring are essential to identify the real bottleneck.

Why does async improve scalability?

Many people think async makes code faster.

Important

Async doesn't necessarily make an operation faster.

It makes better use of server resources.

Synchronous Request

public string GetData()
{
    var data = apiClient.GetData();
    return data;
}

Thread waits:

Thread 1
   |
   | Waiting...
   | Waiting...
   | Waiting...

During waiting, the thread cannot serve other requests.

Asynchronous Request

public async Task<string> GetData()
{
    return await apiClient.GetDataAsync();
}
Thread starts work
      ↓
I/O operation starts
      ↓
Thread returned to pool
      ↓
I/O completes
      ↓
Thread resumes execution

The thread is free to handle other requests while waiting.

Example

Suppose:

  • 100 requests.

  • Each waits 2 seconds for database.

Synchronous

Many threads remain blocked.

100 Threads Busy

Asynchronous

Threads are released during I/O waits.

10-20 Threads handling
100 Requests

Much better scalability.

InShort

Async improves scalability because it releases threads while waiting for I/O operations such as database calls, file access, or HTTP requests. This allows the server to handle more concurrent requests with the same hardware resources. Async improves throughput and resource utilization, though it does not necessarily make individual operations execute faster.

Summary

Understanding why DbContext is registered as Scoped, avoiding lifetime mismatches in dependency injection, using caching carefully, identifying production performance bottlenecks, and leveraging asynchronous programming appropriately are fundamental concepts for building scalable and reliable ASP.NET Core applications. These practices help improve performance, maintain thread safety, optimize resource utilization, and reduce common issues in production environments.