Skip to content

Cookies on Jobverse

We use cookies that are needed to sign you in and keep the site working. With your permission we’d also use an analytics cookie to count job views, and Google advertising cookies to show ads. The site works the same whatever you choose. Read the Cookie Policy.

Jobverse
Sign in

.NET Backend Developer Top 100 Interview Questions

100 .NET backend developer interview questions with structured answers, comparison tables, flow diagrams, worked calculations and runnable C# code. Covers the language and runtime, async and concurrency, the web pipeline, data access, caching, security, testing, diagnostics, performance and system design, from the basics to production incidents.

100 questions

Questions 61-80 of 100

  1. Q61.How do you handle concurrent updates with optimistic concurrency?

    Optimistic concurrency lets many users read and edit the same record without locks, and detects conflicts at write time. Each row has a version value; an update succeeds only if the version is unchanged since it was read, and it increments the version. If another update happened first, zero rows are affected, and the application reports a conflict.

    The update pattern: UPDATE products SET stock = @stock, version = version + 1 WHERE id = @id AND version = @version, then check the affected row count.

    Handling a conflict:

    1. Retry automatically for operations that can be recomputed, such as decrementing stock: reload, reapply and save again.
    2. Return 409 Conflict to the client for human edits, so the user can review the latest data.
    3. Merge field by field only when the rules are clear.

    When to prefer pessimistic locking: very high contention on the same rows, such as a single counter updated by everyone, where retries would waste work. Even then, consider redesigning, for example with atomic UPDATE ... SET stock = stock - 1 WHERE stock > 0.

    In APIs, expose the version as an entity tag header, so clients send it back with updates.

    Example

    Two users read version 1; the first update succeeds, the second is rejected and succeeds after reloading.

    var store = new VersionedStore();
    store.Insert(new Product(1, "Lamp", Stock: 10, Version: 1));
    
    Product a = store.Get(1);
    Product b = store.Get(1);
    Console.WriteLine(store.TryUpdate(a with { Stock = a.Stock - 3 })); // True
    Console.WriteLine(store.TryUpdate(b with { Stock = b.Stock - 5 })); // False
    
    Product fresh = store.Get(1);
    Console.WriteLine(store.TryUpdate(fresh with { Stock = fresh.Stock - 5 })); // True
    Console.WriteLine(store.Get(1)); // Product { Id = 1, Name = Lamp, Stock = 2, Version = 3 }
    
    record Product(int Id, string Name, int Stock, int Version);
    
    class VersionedStore
    {
        private readonly Dictionary<int, Product> rows = [];
    
        public void Insert(Product p) => rows[p.Id] = p;
        public Product Get(int id) => rows[id];
    
        public bool TryUpdate(Product changed)
        {
            if (rows[changed.Id].Version != changed.Version) return false;
            rows[changed.Id] = changed with { Version = changed.Version + 1 };
            return true;
        }
    }
  2. Q62.How do you cache data in a .NET service?

    Cache data that is read often, changes rarely and is expensive to produce. Choose between a per-process cache and a shared cache based on consistency needs and the number of instances.

    In-memory cacheDistributed cache
    Lives inside each processLives in a shared external store
    Fastest, with no network callAdds a network round trip
    Each instance has its own copy, so data can differ between instancesAll instances see the same values
    Best for reference data and expensive computationsBest for sessions, shared results and data that must be consistent

    Design points:

    1. Expiry: absolute and sliding expirations, matched to how stale the data may be.
    2. Size limits, so the cache cannot grow without bound.
    3. Stampede protection: when an entry expires, let one caller reload it while others wait; the newer hybrid cache abstraction in .NET does this automatically and combines both cache levels.
    4. Invalidation: remove or update entries when the source changes, or publish change events to all instances.
    5. Keys: include every input that affects the value, such as tenant and language.

    Monitor hit rates; a cache with a low hit rate adds complexity without benefit.

    Example

    A small cache with a time provider expires an entry after its lifetime and reloads it once.

    var clock = new ManualClock(new DateTimeOffset(2026, 3, 1, 9, 0, 0, TimeSpan.Zero));
    var cache = new ExpiringCache<string, string>(clock, TimeSpan.FromMinutes(5));
    int loads = 0;
    
    string Load(string key) { loads++; return $"{key} v{loads}"; }
    
    Console.WriteLine(cache.GetOrAdd("rates", Load)); // rates v1
    clock.Advance(TimeSpan.FromMinutes(3));
    Console.WriteLine(cache.GetOrAdd("rates", Load)); // rates v1
    clock.Advance(TimeSpan.FromMinutes(3));
    Console.WriteLine(cache.GetOrAdd("rates", Load)); // rates v2
    
    class ManualClock(DateTimeOffset start) : TimeProvider
    {
        private DateTimeOffset now = start;
        public override DateTimeOffset GetUtcNow() => now;
        public void Advance(TimeSpan by) => now += by;
    }
    
    class ExpiringCache<TKey, TValue>(TimeProvider time, TimeSpan lifetime) where TKey : notnull
    {
        private readonly Dictionary<TKey, (TValue Value, DateTimeOffset Expires)> entries = [];
        private readonly Lock gate = new();
    
        public TValue GetOrAdd(TKey key, Func<TKey, TValue> load)
        {
            lock (gate)
            {
                if (entries.TryGetValue(key, out var e) && time.GetUtcNow() < e.Expires) return e.Value;
                TValue value = load(key);
                entries[key] = (value, time.GetUtcNow() + lifetime);
                return value;
            }
        }
    }
  3. Q63.How do authentication and authorization work in a .NET web service?

    Authentication establishes who the caller is; authorization decides what that caller may do. In .NET web services, authentication middleware validates credentials, such as a bearer token, and creates a user principal with claims; authorization then checks those claims against policies on each endpoint.

    sequenceDiagram
      participant C as Client
      participant A as Authentication middleware
      participant Z as Authorization
      participant E as Endpoint
      C->>A: Request with bearer token
      A->>A: Validate signature, issuer, audience, expiry
      A->>Z: User principal with claims
      Z->>Z: Evaluate the endpoint's policy
      Z->>E: Allowed
      E-->>C: Response
      Z-->>C: 401 if unauthenticated, 403 if not permitted

    Good practices:

    1. Validate tokens fully: signature, issuer, audience and lifetime, with small clock skew allowances.
    2. Policy-based authorization: name policies, such as "CanRefundOrders", built from roles, scopes or custom requirements, instead of checking roles in every handler.
    3. Resource-based checks: confirm that the user may act on this specific order, not just on orders in general.
    4. Deny by default: require authorization globally, and mark public endpoints explicitly.
    5. Short-lived tokens with refresh handled by the identity provider.
  4. Q64.How do you manage secrets and sensitive configuration in .NET services?

    Keep secrets out of source code and settings files, inject them at run time from a secret store or the platform, give each service only the secrets it needs and rotate them regularly.

    Where secrets should come from:

    • Local development: the user secrets store kept outside the project folder, or local environment variables.
    • Production: a managed secret store, mounted files or environment variables set by the deployment platform.
    • Managed identities where the platform supports them, so the service gets credentials without storing any secret at all.

    Practices:

    1. Bind secrets through configuration like any other setting, so code does not care where they come from.
    2. Never log configuration objects that contain secrets; mark sensitive properties and redact them.
    3. Scan repositories for committed secrets, and revoke any found immediately, since history keeps them.
    4. Rotate without downtime: support two valid values during rotation, such as current and next keys.
    5. Protect data at rest with the platform's data protection features, and keep their keys in shared, persistent storage when running several instances.
  5. Q65.How should a .NET service write structured logs?

    Use the logging abstraction that the host provides, write message templates with named placeholders and send output to a structured sink, such as JSON on standard output. Each placeholder becomes a searchable field, so "Order {OrderId} failed" produces both readable text and an OrderId field.

    Good practices:

    1. Message templates, not interpolation: logger.LogInformation("Order {OrderId} placed", id) keeps the field structured; $"Order {id}" loses it.
    2. Log levels used consistently: errors for failures needing attention, warnings for unusual but handled cases, information for key business events and debug for diagnostics.
    3. Scopes for context: add request ID, user ID and tenant once per request, and every log line inside includes them.
    4. Source-generated logging methods for hot paths, which avoid boxing and parsing costs and check templates at compile time.
    5. Never log secrets or sensitive personal data; redact fields centrally.
    6. Log exceptions once, at the boundary where they are handled, passing the exception object so the stack trace is kept.

    Correlation: include trace and span IDs, so logs can be joined with distributed traces across services.

  6. Q66.How do you add tracing and metrics with `System.Diagnostics`?

    .NET has built-in types for observability that exporters send to monitoring systems. ActivitySource and Activity create spans for distributed tracing; Meter creates instruments, such as counters and histograms, for metrics. The web stack, HTTP clients and many libraries already emit them.

    Tracing:

    • Create one ActivitySource per component, and start an activity around significant operations.
    • Activities nest automatically: an activity started inside another becomes its child, and trace context flows to outgoing HTTP calls.
    • Add tags for important attributes, such as order ID or result.

    Metrics:

    • Counters for events, such as orders placed.
    • Histograms for distributions, such as request duration.
    • Observable gauges for current values, such as queue length.

    Practices:

    1. Keep tag values low-cardinality on metrics; put identifiers in traces instead.
    2. Name sources and meters consistently, so exporters can enable them by name.
    3. Sample traces at high traffic, but keep errors and slow requests.

    Nothing is collected unless a listener or exporter subscribes, so instrumenting code costs very little when monitoring is off.

    Example

    Listeners print spans as they end and add up a counter; the child span ends first and knows its parent.

    using System.Diagnostics;
    using System.Diagnostics.Metrics;
    
    var source = new ActivitySource("Shop.Orders");
    using var listener = new ActivityListener
    {
        ShouldListenTo = s => s.Name == "Shop.Orders",
        Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
        ActivityStopped = a => Console.WriteLine(
            $"{a.DisplayName} child={a.Parent is not null} tags={string.Join(",", a.Tags.Select(t => $"{t.Key}={t.Value}"))}"),
    };
    ActivitySource.AddActivityListener(listener);
    
    using (var parent = source.StartActivity("PlaceOrder"))
    {
        parent?.SetTag("order.id", "42");
        using var child = source.StartActivity("ChargeCard");
        child?.SetTag("amount", "1250");
    }
    
    var meter = new Meter("Shop.Orders");
    Counter<int> placed = meter.CreateCounter<int>("orders.placed");
    int total = 0;
    using var meterListener = new MeterListener
    {
        InstrumentPublished = (instrument, l) =>
        {
            if (instrument.Meter.Name == "Shop.Orders") l.EnableMeasurementEvents(instrument);
        },
    };
    meterListener.SetMeasurementEventCallback<int>((_, value, _, _) => total += value);
    meterListener.Start();
    placed.Add(1);
    placed.Add(2);
    Console.WriteLine($"orders.placed={total}");
    // Output:
    // ChargeCard child=True tags=amount=1250
    // PlaceOrder child=False tags=order.id=42
    // orders.placed=3
  7. Q67.How do health checks work in a .NET service?

    Health checks are endpoints that report whether the service is working, used by load balancers and orchestrators to route traffic and restart unhealthy instances. The .NET web stack includes a health check system where you register named checks and map them to endpoints.

    Separate the questions being asked:

    • Liveness: "is the process running and not stuck?" A simple check with no dependencies; failure leads to a restart.
    • Readiness: "can this instance serve traffic now?" Includes startup completion and critical local resources; failure removes the instance from load balancing.
    • Dependency status: checks on databases or external services for dashboards and alerts.

    Good practices:

    1. Do not put shared dependencies in liveness checks: a database outage would restart every instance, turning a partial failure into a total one.
    2. Use tags to group checks and map different endpoints to different groups.
    3. Keep checks fast and cached, since they run frequently.
    4. Protect detailed output: a public endpoint should return only status, while detailed results stay internal.
    5. Report degraded states where a dependency is slow but usable.
  8. Q68.How do you write unit tests for .NET backend code?

    Test behavior through public methods with small, fast, isolated tests, using fakes for external dependencies. The common .NET test frameworks share the same structure: test classes, test methods marked with attributes, assertions and data-driven tests with inline cases.

    Structure each test as arrange, act and assert:

    1. Arrange: create the object under test with fake dependencies and input data.
    2. Act: call one method.
    3. Assert: check the result, the state change or the interaction that matters.

    Good practices:

    • Name tests by behavior, such as "Refund fails when order is already refunded".
    • Data-driven tests for many input cases of the same rule.
    • Inject time, randomness and identifiers, so results are deterministic.
    • Avoid testing private methods directly; test through the public behavior.
    • Async tests return Task and await; never block on .Result.
    • Test failure paths: exceptions, timeouts and cancellation.

    Where unit tests stop: database queries, serialization and HTTP pipelines need integration tests with real components, such as a real database in a container and an in-memory test server.

  9. Q69.How do you test HTTP endpoints in a .NET service?

    Run the real application in memory with a test host, then send HTTP requests to it through a client the host provides. The whole pipeline runs, including routing, model binding, serialization, filters and middleware, without opening network ports or deploying anything.

    How it is set up:

    1. A test fixture starts the application from its entry point with a test environment.
    2. The fixture can replace services, such as swapping a payment gateway for a fake or pointing the database at a test instance.
    3. Tests create a client from the fixture and call endpoints with ordinary HTTP methods.

    What to test at this level:

    • Status codes, headers and response bodies for success and error cases.
    • Authentication and authorization rules, with test authentication handlers.
    • Validation responses and the problem details format.
    • Serialization settings, such as naming and enum formats.

    Making it reliable:

    1. Use a real database engine in a container, reset between tests, rather than an in-memory substitute with different behavior.
    2. Keep tests independent so they can run in parallel.
    3. Share one host per test class to keep runs fast.
  10. Q70.How do you replace dependencies with fakes in .NET tests?

    Depend on small interfaces in the code under test, then supply hand-written fakes in tests that return chosen results and record what happened. Fakes keep tests fast and deterministic and let you test failure paths that are hard to trigger with real systems.

    Kinds of test doubles:

    • Fakes: lightweight working implementations, such as an in-memory repository.
    • Stubs: return fixed responses or throw chosen exceptions.
    • Spies: record calls for later checks.

    Guidelines:

    1. Fake at the boundary: external services, databases, clocks and message brokers, not your own domain classes.
    2. Prefer checking outcomes over verifying every call, which makes tests brittle when implementation details change.
    3. Keep fakes simple and shared across tests, so they stay consistent.
    4. Mocking libraries help with large interfaces, but hand-written fakes are often clearer for small ones.
    5. Back fakes with contract tests that run the same scenarios against the real implementation occasionally.

    Example

    A fake payment gateway records charges and can be told to fail, so both paths are tested.

    var gateway = new FakeGateway();
    var service = new CheckoutService(gateway);
    
    Console.WriteLine(await service.CheckoutAsync("ord-1", 1250m)); // paid
    gateway.FailNext = true;
    Console.WriteLine(await service.CheckoutAsync("ord-2", 900m));  // payment failed: card declined
    Console.WriteLine(string.Join(",", gateway.Charges));            // ord-1:1250
    
    interface IPaymentGateway
    {
        Task ChargeAsync(string orderId, decimal amount, CancellationToken ct);
    }
    
    class CheckoutService(IPaymentGateway payments)
    {
        public async Task<string> CheckoutAsync(string orderId, decimal amount, CancellationToken ct = default)
        {
            try
            {
                await payments.ChargeAsync(orderId, amount, ct);
                return "paid";
            }
            catch (PaymentDeclinedException e)
            {
                return $"payment failed: {e.Message}";
            }
        }
    }
    
    class PaymentDeclinedException(string message) : Exception(message);
    
    class FakeGateway : IPaymentGateway
    {
        public bool FailNext { get; set; }
        public List<string> Charges { get; } = [];
    
        public Task ChargeAsync(string orderId, decimal amount, CancellationToken ct)
        {
            if (FailNext)
            {
                FailNext = false;
                throw new PaymentDeclinedException("card declined");
            }
            Charges.Add($"{orderId}:{amount}");
            return Task.CompletedTask;
        }
    }
  11. Q71.How do you structure a .NET solution as it grows?

    Organize code by business capability, keep dependencies pointing inward toward the domain and let the web project compose everything. Separate projects enforce boundaries the compiler can check, but too many projects add friction, so split only where the boundary is real.

    flowchart TD
      A["Api project: endpoints, middleware, composition"] --> B["Orders module: domain and use cases"]
      A --> C["Payments module: domain and use cases"]
      B --> D["Infrastructure: database and messaging adapters"]
      C --> D
      E["Tests per module"] --> B
      E --> C

    Principles:

    1. Modules by feature, such as orders and payments, each with its domain, use cases and data access, rather than global layers named services and repositories.
    2. Domain code free of infrastructure: no web or database types in business rules, which keeps them easy to test.
    3. Interfaces owned by the consumer, implemented in infrastructure.
    4. The entry project wires dependencies and maps endpoints; it contains no business logic.
    5. Internal visibility: mark implementation types internal so other modules cannot depend on them.

    A modular monolith first: clear modules inside one deployable often deliver most of the benefits of microservices with far less operational cost, and modules can be extracted later if needed.

  12. Q72.How do you version and document an HTTP API built with .NET?

    Publish a machine-readable description of the API generated from the code, version the API explicitly when you make breaking changes and keep old versions running long enough for clients to migrate.

    Documentation:

    1. Generate the description from endpoints, including routes, parameters, request and response types and status codes, so it never drifts from the code.
    2. Add summaries and examples to types and endpoints for clarity.
    3. Publish it to client teams, and generate client code or contract tests from it.

    Versioning strategies:

    • URL segment: /v2/orders, the most visible and easiest to route.
    • Header or media type: keeps URLs stable but is harder to test by hand.
    • Query string: simple, but easy to forget.

    Avoiding breaking changes:

    • Add fields instead of renaming or removing them.
    • Make new request fields optional with defaults.
    • Never change the meaning of an existing field.
    • Deprecate with announcements, warning headers and usage metrics before removal.

    Contract tests in continuous integration compare the generated description with the previous release and flag breaking changes automatically.

  13. Q73.How do you implement graceful shutdown in a .NET host?

    When the host receives a termination signal, it stops accepting new requests, lets in-flight requests and background services finish within a timeout, then disposes services in reverse order of creation. Your code must cooperate by honoring cancellation tokens and finishing work quickly.

    sequenceDiagram
      participant P as Platform
      participant H as Host
      participant S as Web server
      participant B as Background services
      P->>H: Termination signal
      H->>H: Raise application stopping
      H->>S: Stop accepting, finish in-flight requests
      H->>B: Cancel stopping tokens
      B-->>H: Finish or checkpoint work
      H->>H: Dispose services, flush logs
      H-->>P: Process exits

    What to configure and write:

    1. Shutdown timeout: set the host's shutdown timeout below the platform's grace period, so the process exits cleanly before it is killed.
    2. Readiness: fail readiness checks as soon as stopping begins, and allow a short delay so load balancers stop sending traffic.
    3. Background services: pass the stopping token everywhere, and drain or checkpoint queued work.
    4. Disposal: implement IAsyncDisposable on services that flush buffers, such as telemetry exporters.
    5. Long-lived connections: close WebSockets and streaming responses with proper close messages.
  14. Q74.How do you package and run a .NET service in a container?

    Publish the application in a build stage, copy the output into a small runtime image, run as a non-root user and configure the runtime to respect container limits. The SDK can also build container images directly from the project without a separate image definition file.

    1. Multi-stage builds: restore and publish with the SDK image, then copy only the published output into a runtime image.
    2. Restore layer caching: copy project files and restore before copying source, so code changes do not repeat package downloads.
    3. Smaller images: use minimal runtime images, and consider trimmed or ahead-of-time compiled builds for very small, fast-starting images.
    4. Non-root user: recent official images include a non-root user and listen on a non-privileged port by default.
    5. Resource awareness: the runtime reads container memory and processor limits; set memory limits explicitly, and consider garbage collection settings that adapt the heap count to the load.
    6. Configuration from the environment: connection strings and settings through environment variables or mounted secrets.
    7. Health checks exposed for the orchestrator.

    Diagnostics: minimal images lack shells and tools, so plan how to collect dumps and traces, for example with a diagnostic sidecar sharing the process's diagnostic socket.

  15. Q75.How do you handle dates, times and time zones in .NET?

    Store and exchange instants as UTC, preferably with DateTimeOffset, which records the offset explicitly. Use DateOnly and TimeOnly for calendar dates and times of day that are not instants, and convert to a user's time zone only for display.

    DateTimeOffsetDateTimeDateOnly and TimeOnly
    An instant with an explicit offset from UTCA date and time whose kind may be UTC, local or unspecifiedA calendar date, or a time of day, without a zone
    Unambiguous when stored or sentEasy to misuse when the kind is unspecifiedNo conversion errors for birthdays or opening hours
    Best for timestamps in APIs and databasesBest kept for legacy code and UTC-only valuesBest for business dates and schedules

    Practical rules:

    1. Use TimeProvider or UTC methods for "now", never local time on servers.
    2. Serialize in the round-trip format, such as 2026-03-01T09:30:00+00:00.
    3. Convert with TimeZoneInfo using zone IDs, which handle daylight saving rules.
    4. Be careful with recurring local events across daylight saving changes; store the local time and zone, not a computed UTC time.

    Example

    An instant converted for display, a calendar date and the pitfall of an unspecified kind.

    var instant = new DateTimeOffset(2026, 3, 1, 9, 30, 0, TimeSpan.Zero);
    TimeZoneInfo india = TimeZoneInfo.FindSystemTimeZoneById("Asia/Kolkata");
    
    Console.WriteLine(TimeZoneInfo.ConvertTime(instant, india).ToString("yyyy-MM-dd HH:mm zzz")); // 2026-03-01 15:00 +05:30
    Console.WriteLine(instant.ToString("O"));                       // 2026-03-01T09:30:00.0000000+00:00
    Console.WriteLine(new DateOnly(2026, 3, 1).DayOfWeek);          // Sunday
    Console.WriteLine(new DateTime(2026, 3, 1, 9, 30, 0).Kind);     // Unspecified
  16. Q76.A .NET service's latency jumps under load while processor use stays moderate. What do you suspect?

    Thread pool starvation is the classic cause: request threads are blocked waiting, so new work and the continuations of awaited operations queue up. The pool adds threads only gradually, so latency climbs for minutes before it catches up, if it ever does.

    How to confirm it:

    1. Runtime counters show the thread pool thread count rising steadily and a growing queue length.
    2. A dump or stack snapshot shows many threads waiting in .Result, .Wait(), Thread.Sleep, locks or synchronous input and output.
    3. Latency improves temporarily after a restart, then degrades again under the same load.

    Common sources:

    • Sync-over-async calls in request code or in libraries.
    • Synchronous database or file calls in hot paths.
    • Long CPU-bound work on request threads.
    • lock sections that wrap slow operations.

    Fixes: make the call chain asynchronous end to end, replace blocking calls, move CPU-heavy work to a bounded background pipeline and treat raising the minimum thread count only as a temporary mitigation.

    Example

    A service receives 200 requests per second, and each request blocks a thread for 500 ms on .Result while waiting for a downstream call.

    1. Threads needed: 200 × 0.5 = 100 blocked threads at any moment.
    2. The pool starts with about as many threads as cores, here 8, and adds new ones slowly, roughly one or two per second.
    3. Reaching 100 threads takes about a minute, and during that time requests queue behind the blocked ones, so latency grows to many seconds.
    4. Awaiting the downstream call instead releases each thread during the wait, and the same traffic runs on a handful of threads.
  17. Q77.Memory in a .NET service grows steadily after a new feature ships. How do you find the cause?

    Compare heap snapshots over time to see which object types grow, then follow their retention paths to the root that keeps them alive. Steady growth after a release usually comes from something the feature added that holds references longer than intended.

    1. Confirm the pattern: generation 2 size rising across collections at steady traffic.
    2. Take two heap snapshots, some time apart, and compare counts and sizes by type.
    3. Inspect the growing type's paths to roots: a static collection, a singleton, an event or a timer is usually at the top.
    4. Review the feature's code for those patterns.

    Typical culprits after a feature release:

    • An event subscription from a per-request or per-message object to a long-lived publisher, never removed.
    • A dictionary used as a cache with no expiry or size limit.
    • A singleton capturing a scoped service.
    • Timers created per item and never disposed.
    • Large arrays allocated per request that fragment the large object heap.

    Verify the fix: under the same load, memory should level off; add an alert on generation 2 growth so the next leak is caught early.

    Example

    After a notification feature ships, memory grows by about 150 MB per hour.

    1. Comparing snapshots shows millions of OrderViewModel objects retained, each about 100 bytes, plus their data.
    2. Their retention path leads to a singleton PriceFeed service's PriceChanged event.
    3. Each request created a view model that subscribed to the event and was never unsubscribed, so the singleton kept every one alive.
    4. Unsubscribing in Dispose, and creating view models in a scope that disposes them, stops the growth.
  18. Q78.Outgoing HTTP calls start failing with socket errors under load. What is a likely cause in .NET code?

    A frequent cause is creating a new HttpClient for each request and disposing it. Each client has its own connection pool, so connections are not reused, and closed sockets linger in a waiting state, eventually exhausting available ports or the server's connection limits.

    Symptoms:

    • Errors such as "only one usage of each socket address is normally permitted" or connection refused under load.
    • A large number of sockets in the post-close waiting state toward the same destination.
    • Latency spikes from repeated connection and TLS handshakes.

    The fix:

    1. Reuse clients: register clients through the client factory, or use a long-lived client.
    2. Refresh connections periodically with a pooled connection lifetime, so changes in name resolution are respected.
    3. Size connection limits for your concurrency, such as maximum connections per server.

    Other causes to check:

    • Response streams that are never disposed, keeping connections busy.
    • Missing timeouts, so slow calls hold connections for a long time.
    • Very high concurrency without limits toward one dependency.
  19. Q79.An endpoint that lists orders became slow as data grew. What do you check first?

    Count the database queries the endpoint runs. Slow list endpoints in .NET services are very often caused by N+1 queries, where loading a list triggers one more query per item, usually through lazy loading of related data.

    How to investigate:

    1. Enable query logging in development, or use tracing, to see every query for one request.
    2. Look for repeated queries that differ only by an ID.
    3. Check what is loaded: whole entities with many columns, or related collections that the response does not use.
    4. Check paging: whether the endpoint loads all rows and pages in memory.
    5. Check indexes for the filter and sort columns.

    Fixes:

    • Project to a response shape with Select, which loads only the needed columns, often in one query.
    • Load related data explicitly with includes or split queries, instead of lazy loading.
    • Page in the database, with keyset paging for large tables.
    • Use no-tracking queries for read-only endpoints.
    • Add or adjust indexes.

    Example

    A page shows 50 orders, each with its customer and line items, loaded lazily.

    1. Queries per request: 1 + 50 × 2 = 101.
    2. At about 2 ms each, that is roughly 200 ms of database time, plus network round trips.
    3. A single projection query that joins customers and aggregates line items replaces them with 1 query of about 8 ms.
    4. The endpoint's latency drops from about 250 ms to about 30 ms, and the database load falls by a factor of roughly 25.
  20. Q80.A .NET service shows frequent, long garbage collection pauses. What could be causing them?

    Long pauses usually mean frequent generation 2 collections, often triggered by large allocations or by a growing set of long-lived objects. The large object heap is a common culprit, because objects of about 85 KB or more go there directly and are collected only with generation 2.

    Common causes:

    • Large buffers per request, such as reading whole request bodies or files into byte arrays.
    • Large strings and serialization buffers for big JSON payloads.
    • Growing caches that keep promoting objects into generation 2.
    • Fragmentation of the large object heap from many differently sized large arrays.

    How to confirm: runtime counters for generation 2 collection rate, large object heap size and time in garbage collection; allocation traces show which code allocates large objects.

    Fixes:

    1. Pool large buffers with ArrayPool.
    2. Stream requests, responses and files instead of buffering them whole.
    3. Use spans and pipelines for parsing, avoiding large intermediate copies.
    4. Bound caches.
    5. Check the collector mode: server collection with appropriate heap settings for services.

    Example

    Each request reads a 1 MB file into a new byte array, at 500 requests per second.

    1. Large object heap allocations: about 500 × 1 MB = 500 MB per second.
    2. The runtime triggers generation 2 collections every few seconds to reclaim them, each pausing request threads.
    3. Renting the buffer from ArrayPool and streaming the file in 64 KB chunks removes almost all large allocations.
    4. Generation 2 collections drop from several per minute to a few per hour, and the 99th percentile latency falls accordingly.