Diagnosing Slow Angular, ASP.NET Core and SQL Server Systems
Below is the kind of senior–junior conversation you can use for interview preparation, mentoring or real production diagnosis.
The question is:
“Our production application is very slow. It uses Angular, NgRx, ASP.NET Core Web API, CQRS, Repository Pattern, SQL Server and stored procedures. How would you investigate and fix it?”
Senior, we have a serious problem. The production application is very slow. Users are complaining that pages take ages to load. Where do we start?
Good. First lesson: do not jump straight into SQL. Do not jump straight into Angular. Do not blame the API. A slow production application is a full journey problem. We need to trace the request from the browser all the way to the database and back.
The stack is:
Angular UI
↓
NgRx state management
↓
HTTP request
↓
ASP.NET Core Web API
↓
Controller / Minimal endpoint
↓
CQRS command/query handler
↓
Repository
↓
Stored procedure
↓
SQL Server
↓
Response back to UI
A senior developer investigates the whole path.
So the first step is to reproduce the issue?
Yes, but carefully. In production, users may say “the app is slow”, but that is too vague. We need to ask: which screen is slow? Is it slow for all users or only some users? Is it slow all the time or only during peak hours? Is it slow on first load or every load? Is it slow after login? Is saving slow, searching slow, exporting slow, or navigating slow?
For example, “the customer dashboard is slow” is more useful than “the system is slow”.
So step one is: define the slow user journey.
Let’s say the slow journey is opening the “Customer Orders” page.
Good. Now we split the journey into timings. The browser is our first evidence source. Open Chrome DevTools, go to the Network tab, reload the page, and inspect the API calls.
We want to know:
Is the Angular bundle slow to load?
Is the API call slow?
Is the response payload huge?
Are there too many API calls?
Are calls happening sequentially when they could happen in parallel?
Is the browser rendering slow after data arrives?
If the API call takes 8 seconds, the backend/database may be the problem. If the API responds in 200ms but the page still freezes, Angular rendering or NgRx state handling may be the problem.
So we measure at the frontend first?
Exactly. Production slowness must be measured, not guessed.
In DevTools, look at:
DOMContentLoaded
Load time
API request duration
Response size
Waterfall timing
Number of requests
Failed/retried requests
If you see one API call taking 12 seconds, that is a clear backend candidate. If you see 80 API calls, each taking 100ms, the problem may be chatty frontend design.
What about Angular and NgRx specifically?
Angular and NgRx can create performance issues in several ways.
One issue is dispatching too many actions. Another is selectors recalculating too often. Another is loading too much data into the store. Another is rendering large lists without virtual scrolling or pagination. Another is multiple components triggering the same API call.
Let’s say the page dispatches:
this.store.dispatch(loadCustomerOrders({ customerId }));
That triggers an effect:
loadCustomerOrders$ = createEffect(() =>
this.actions$.pipe(
ofType(loadCustomerOrders),
switchMap(action =>
this.ordersApi.getCustomerOrders(action.customerId).pipe(
map(orders => loadCustomerOrdersSuccess({ orders })),
catchError(error => of(loadCustomerOrdersFailure({ error })))
)
)
)
);
This looks fine. But we need to check whether this action is dispatched once or multiple times. Sometimes ngOnInit, route resolver, parent component, child component, and refresh logic all dispatch the same load action.
How do we check that?
Use Redux DevTools or NgRx Store DevTools. Watch the actions. If you see loadCustomerOrders firing five times for one page load, you have a frontend state issue.
Also check selectors.
Bad selector usage can cause too much recalculation or too much rendering. For example, if a selector returns a new array every time without memoisation discipline, Angular may rerender unnecessarily.
Also check whether the component uses OnPush change detection.
@Component({
selector: 'app-customer-orders',
templateUrl: './customer-orders.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomerOrdersComponent {
}
OnPush helps Angular avoid unnecessary checks, especially in large screens.
What about large tables?
Very important. If the API returns 50,000 orders and Angular renders them all, the page will be slow even if SQL is fast. The fix is not only backend optimisation. The fix is pagination, filtering, and virtual scrolling.
A better API request is:
GET /api/customers/123/orders?pageNumber=1&pageSize=50&sortBy=orderDate&sortDirection=desc
Not:
GET /api/customers/123/orders
returning everything.
So once frontend is checked, we go to the API?
Yes. Now we trace the backend request.
In ASP.NET Core, we need structured logging and correlation IDs. When the Angular app calls the API, we should be able to trace one request across the layers.
A useful log flow is:
Request started: GET /api/customers/123/orders
Controller entered
CQRS query dispatched
Handler started
Repository called
Stored procedure started
Stored procedure completed
Handler completed
Response returned
Each log should include a correlation ID.
What is a correlation ID?
It is a unique ID for one request. It helps connect logs across frontend, API, services, and database calls.
Example middleware idea:
app.Use(async (context, next) =>
{
var correlationId = context.Request.Headers["X-Correlation-ID"].FirstOrDefault()
?? Guid.NewGuid().ToString();
context.Response.Headers["X-Correlation-ID"] = correlationId;
using (logger.BeginScope(new Dictionary<string, object>
{
["CorrelationId"] = correlationId
}))
{
await next();
}
});
In production, this is gold. Without correlation, logs become noise.
Suppose the API endpoint itself is slow. How do we inspect it?
Start from the controller.
[HttpGet("{customerId:int}/orders")]
public async Task<IActionResult> GetOrders(
int customerId,
[FromQuery] GetCustomerOrdersRequest request,
CancellationToken cancellationToken)
{
var query = new GetCustomerOrdersQuery(
customerId,
request.PageNumber,
request.PageSize);
var result = await _mediator.Send(query, cancellationToken);
return Ok(result);
}
This controller is thin, which is good. Now the real work happens in the CQRS handler.
public class GetCustomerOrdersQueryHandler
: IRequestHandler<GetCustomerOrdersQuery, PagedResult<OrderDto>>
{
private readonly IOrderRepository _repository;
public GetCustomerOrdersQueryHandler(IOrderRepository repository)
{
_repository = repository;
}
public async Task<PagedResult<OrderDto>> Handle(
GetCustomerOrdersQuery request,
CancellationToken cancellationToken)
{
return await _repository.GetCustomerOrdersAsync(
request.CustomerId,
request.PageNumber,
request.PageSize,
cancellationToken);
}
}
Now inspect the repository.
public async Task<PagedResult<OrderDto>> GetCustomerOrdersAsync(
int customerId,
int pageNumber,
int pageSize,
CancellationToken cancellationToken)
{
// calls stored procedure
}
Questions we ask:
Is the handler doing too much work?
Is it calling multiple repositories?
Are repository calls sequential when independent?
Is it loading more data than needed?
Is mapping expensive?
Is it making N+1 database calls?
Is cancellation passed down?
Is async used correctly?
Is anything using .Result or .Wait()?
Why is .Result dangerous here?
Because .Result blocks a thread. In ASP.NET Core, blocking threads reduces scalability and can create thread pool starvation under load.
Bad:
var orders = _repository.GetCustomerOrdersAsync(customerId).Result;
Good:
var orders = await _repository.GetCustomerOrdersAsync(customerId, cancellationToken);
In production, if enough requests block, the app appears slow even if individual database calls are not terrible.
What about CQRS? Can CQRS make it slow?
CQRS itself is not slow. It is just a pattern. But bad implementation can be slow. For example, a query handler should be optimised for reading. It should not load full domain aggregates if all you need is a dashboard DTO.
Bad read-side approach:
var customer = await _customerRepository.GetCustomerAggregateAsync(customerId);
var orders = customer.Orders.Select(...);
Better read-side approach:
var orders = await _orderReadRepository.GetCustomerOrderSummariesAsync(
customerId,
pageNumber,
pageSize,
cancellationToken);
CQRS gives you permission to optimise queries separately from commands. Use that.
What about Repository Pattern?
Same principle. Repository Pattern is not automatically good or bad. But generic repositories can hide important query details.
A generic method like this:
Task<List<Order>> GetAllAsync();
is dangerous if someone uses it and filters in memory.
Bad:
var allOrders = await _repository.GetAllAsync();
var customerOrders = allOrders.Where(x => x.CustomerId == customerId);
This is terrible if the table has millions of rows.
Better:
Task<PagedResult<OrderDto>> GetCustomerOrdersAsync(
int customerId,
int pageNumber,
int pageSize,
CancellationToken cancellationToken);
A senior repository should expose use-case-specific queries when performance matters.
Now let’s go to the stored procedure.
Yes. Suppose the repository calls:
using var connection = new SqlConnection(_connectionString);
var parameters = new DynamicParameters();
parameters.Add("@CustomerId", customerId);
parameters.Add("@PageNumber", pageNumber);
parameters.Add("@PageSize", pageSize);
var result = await connection.QueryAsync<OrderDto>(
"dbo.GetCustomerOrders",
parameters,
commandType: CommandType.StoredProcedure);
Now we inspect dbo.GetCustomerOrders.
Bad stored procedure:
CREATE PROCEDURE dbo.GetCustomerOrders
@CustomerId INT
AS
BEGIN
SELECT *
FROM Orders
WHERE CustomerId = @CustomerId
ORDER BY OrderDate DESC;
END
This has several problems.
It selects all columns. It has no pagination. It may not have a supporting index. It may return thousands or millions of rows.
Better:
CREATE PROCEDURE dbo.GetCustomerOrders
@CustomerId INT,
@PageNumber INT,
@PageSize INT
AS
BEGIN
SET NOCOUNT ON;
SELECT
Id,
OrderDate,
Status,
TotalAmount
FROM Orders
WHERE CustomerId = @CustomerId
ORDER BY OrderDate DESC
OFFSET (@PageNumber - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;
END
Now the procedure returns only what the screen needs.
What index would support this?
Good question.
CREATE INDEX IX_Orders_CustomerId_OrderDate
ON Orders (CustomerId, OrderDate DESC)
INCLUDE (Status, TotalAmount);
Why this index?
Because the query filters by CustomerId and sorts by OrderDate DESC. The included columns help avoid extra lookups for Status and TotalAmount.
What should we check in the execution plan?
You check whether SQL Server uses an Index Seek or a scan, whether there are Key Lookups, whether there are Sorts, whether actual rows are close to estimated rows, whether there are warnings like spills, missing indexes, implicit conversions, or excessive memory grants.
But do not blindly trust the green missing index suggestion. It is a clue, not a command.
What are logical reads?
Logical reads are how many 8KB pages SQL Server reads from memory. They are one of the best query cost indicators.
Before optimisation:
Duration: 12 seconds
Logical reads: 750,000
Rows returned: 48,000
After pagination and index:
Duration: 40ms
Logical reads: 80
Rows returned: 50
That is a real improvement.
How do we measure logical reads?
In SSMS:
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
EXEC dbo.GetCustomerOrders
@CustomerId = 123,
@PageNumber = 1,
@PageSize = 50;
Look at CPU time, elapsed time, and logical reads.
What if the stored procedure is sometimes fast and sometimes slow?
Then suspect parameter sniffing, blocking, outdated statistics, data skew, or load-related resource pressure.
Parameter sniffing means SQL Server created a plan for one parameter value and reused it for another value where it performs badly.
Example: Customer 123 has 10 orders. Customer 999 has 2 million orders. Same stored procedure, very different data distribution.
Possible mitigations include better indexes, OPTION (RECOMPILE), OPTIMIZE FOR UNKNOWN, dynamic SQL, separate procedures, Query Store plan forcing, or redesign.
But you do not guess. You compare execution plans for different parameter values.
What about blocking?
If elapsed time is high but CPU and reads are low, the query may be waiting. Check blocking.
SELECT
r.session_id,
r.blocking_session_id,
r.wait_type,
r.wait_time,
r.total_elapsed_time,
t.text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.blocking_session_id <> 0;
If there is blocking, the problem may be another transaction holding locks.
Example culprit:
BEGIN TRANSACTION;
UPDATE Orders
SET Status = 'Archived'
WHERE OrderDate < '2020-01-01';
-- transaction left open
Now reads or writes may be blocked.
So application slowness might not be query structure. It could be locks.
Exactly. Production performance is not just query tuning. It is also concurrency, locking, transactions, isolation levels, and workload.
What about stored procedure design?
Stored procedures should be written for the use case. Avoid kitchen-sink procedures that handle every possible filter with many optional parameters like:
WHERE (@Status IS NULL OR Status = @Status)
AND (@CustomerId IS NULL OR CustomerId = @CustomerId)
AND (@FromDate IS NULL OR OrderDate >= @FromDate)
AND (@ToDate IS NULL OR OrderDate <= @ToDate)
This pattern is convenient, but can produce poor plans.
For complex search screens, dynamic SQL with proper parameterisation may produce better plans because SQL Server gets a query shape closer to the actual filters used.
Dynamic SQL sounds dangerous.
String-concatenated SQL with user input is dangerous. Parameterised dynamic SQL is different.
The senior phrase is:
“Dynamic SQL can be valid for complex optional search filters if safely parameterised.”
What about API response size?
Very important. Sometimes SQL is fast, but the API returns 20MB of JSON. Then Angular struggles to parse and render it.
Check:
Response size
JSON serialization time
DTO size
Number of rows
Nested objects
Circular/large object graphs
Bad API response:
{
"customer": {
"orders": [
{
"orderLines": [],
"auditHistory": [],
"payments": [],
"notes": []
}
]
}
}
Better response for dashboard:
{
"items": [
{
"id": 101,
"orderDate": "2026-07-24",
"status": "Pending",
"totalAmount": 150.00
}
],
"pageNumber": 1,
"pageSize": 50,
"totalCount": 1234
}
DTO design is performance design.
How does caching fit in?
Caching can help, but only after you understand the problem. Do not cache broken queries as your first fix.
Good candidates for caching:
Reference data
Dropdown lists
User permissions for a short period
Static configuration
Dashboard summaries
Expensive but not constantly changing reports
Bad candidates:
Highly personalised data
Highly volatile data
Sensitive data without proper keying
Data where stale results are unacceptable
In ASP.NET Core, you might use memory cache for single-instance apps, distributed cache like Redis for scaled apps, or HTTP caching for appropriate GET responses.
But remember cache invalidation. If the data changes, when does the cache update?
What about NgRx caching?
NgRx store can act as a client-side cache. If the user navigates away and back, you may not need to reload immediately.
Example selector:
const selectOrdersLoaded = createSelector(
selectOrdersState,
state => state.loaded
);
Effect can check if data is already loaded before calling API. But be careful with stale data. Sometimes you need refresh policies.
What about Application Insights or monitoring?
Essential. A professional production system should have observability.
You want:
Frontend telemetry
API request duration
Dependency calls to SQL Server
Exception tracking
Slow request traces
SQL dependency duration
Custom logs with correlation ID
Performance counters
Database Query Store
Application Insights can show which API endpoints are slow and which SQL dependencies are slow. SQL Query Store can show which queries are expensive or regressed.
How would you answer this in an interview?
I would say:
“If a production Angular and ASP.NET Core application is slow, I investigate end-to-end. I first identify the exact user journey and measure from the browser using DevTools: bundle size, API waterfall, response size, and rendering behaviour. Then I check NgRx actions and selectors to ensure the app is not dispatching duplicate loads or rendering excessive data. On the backend, I use structured logs, correlation IDs, Application Insights, and endpoint timings to identify the slow API. Then I trace through controller, CQRS handler, repository, and stored procedure. I check whether the handler is doing unnecessary work, whether async is used correctly, whether there are sequential calls that could be parallel, and whether the repository is fetching too much data. At SQL level, I inspect Query Store, actual execution plans, statistics IO/time, blocking, waits, indexes, SARGability, parameter sniffing, and result size. I optimise by reducing data returned, adding pagination, projecting DTOs, improving indexes, rewriting stored procedures if needed, and validating with before/after metrics. Finally, I deploy safely and monitor production after the change.”
That answer sounds senior because it covers the full stack and evidence-based diagnosis.
Nice. Can we make a checklist for the real world?
Yes. Here is the slow production application checklist.
First, identify the slow screen and user journey.
Second, reproduce if possible and record timings.
Third, use browser DevTools to inspect network calls, payload size, waterfall, and frontend rendering.
Fourth, check NgRx actions, effects, selectors, duplicate API calls, and state size.
Fifth, check Angular rendering: large lists, missing pagination, missing virtual scroll, too many subscriptions, unnecessary change detection.
Sixth, identify the slow API endpoint.
Seventh, trace backend logs using correlation ID.
Eighth, inspect controller and CQRS handler.
Ninth, check async usage. Avoid .Result, .Wait(), blocking calls, and unnecessary sequential awaits.
Tenth, inspect repository method. Make sure it fetches exactly what the use case needs.
Eleventh, inspect generated SQL or stored procedure call.
Twelfth, run the stored procedure with real parameters in a safe environment.
Thirteenth, capture actual execution plan.
Fourteenth, measure STATISTICS IO and STATISTICS TIME.
Fifteenth, check indexes, scans, seeks, Key Lookups, Sorts, spills, implicit conversions, estimated vs actual rows.
Sixteenth, check blocking and wait types.
Seventeenth, check parameter sniffing and statistics.
Eighteenth, optimise query shape, indexes, pagination, projection, and stored procedure logic.
Nineteenth, test before and after with the same parameters.
Twentieth, deploy carefully, monitor Query Store/Application Insights, and confirm user experience improved.
What are the biggest mistakes people make?
The biggest mistakes are guessing, adding indexes blindly, blaming SQL before checking frontend, returning too much data, using SELECT *, ignoring pagination, hiding bad queries behind generic repositories, using EF lazy loading accidentally, ignoring execution plans, not checking blocking, not checking parameter sniffing, and making production changes without measuring before and after.
Also, many developers optimise the wrong thing. They spend two hours shaving 20ms from a query while the Angular page renders 30,000 rows and freezes for 8 seconds.
So performance is a chain?
Exactly. The user does not care that SQL took only 100ms if the page takes 10 seconds to become usable. Performance is the whole chain.
Browser load
Angular rendering
NgRx state
API latency
C# code
Database query
Network transfer
Serialization
Deserialization
UI rendering
A senior developer follows the chain.
What if the issue happens only at peak time?
Then you need production telemetry. Peak-time slowness may be caused by load, connection pool exhaustion, thread pool starvation, database CPU pressure, blocking, tempdb pressure, memory pressure, or downstream service latency.
Check:
API CPU/memory
Thread pool starvation symptoms
SQL CPU
SQL waits
Connection pool usage
Average request duration
Failed requests
Queue length
Database deadlocks/blocking
Long-running transactions
Also check deployment timing. Did the issue start after a release? Did a new feature add a query? Did an index get dropped? Did data volume grow?
What about stored procedures versus EF queries? Which is faster?
That is an interview trap. Stored procedures are not automatically faster. EF is not automatically slower. Bad SQL is bad SQL whether generated by EF or written by hand.
Stored procedures are useful when you want tight control, complex reporting, security boundaries, or existing DBA-managed logic. EF is excellent for normal application queries if used correctly with projection, pagination, AsNoTracking, and proper indexes.
The real question is: what SQL runs, what plan does it produce, and how much work does it do?
At the end, what should I be comfortable saying?
Say this:
“When a production application is slow, I do not guess. I trace the full request path from Angular to SQL Server. I measure browser timings, NgRx behaviour, API latency, backend logs, CQRS handler work, repository data access, stored procedure performance, execution plans, indexes, waits, blocking, and payload size. I identify whether the bottleneck is frontend rendering, network, API code, database query, blocking, or infrastructure. Then I reduce unnecessary work, fetch less data, add pagination, optimise indexes, improve stored procedures, fix async/blocking issues, and validate before/after metrics. Performance tuning is not one trick. It is disciplined investigation.”
That is the master mindset.
A Repeatable Production-Diagnosis Playbook
1. Turning “slow” into a measurable symptom
Turning “slow” into a measurable symptom matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Begin with a real scenario and the evidence currently available. Naming the observable symptom prevents the discussion from collapsing into framework preference.
Junior developer asks: “How do I know whether I have turned ‘slow’ into a measurable symptom?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for turning “slow” into a measurable symptom: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
2. Establishing a request correlation ID
Establishing a request correlation ID matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Model the boundary before choosing a tool. Inputs, outputs, ownership and failure behaviour determine which implementation is appropriate.
Junior developer asks: “How do I know whether establishing a request correlation id is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for establishing a request correlation id: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
3. Building an end-to-end latency budget
Building an end-to-end latency budget matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Prefer a thin experiment over a broad assumption. A small representative measurement often resolves questions that documentation alone cannot answer.
Junior developer asks: “How do I know whether building an end-to-end latency budget is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for building an end-to-end latency budget: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
4. Using browser network timings
Using browser network timings matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Design for maintenance under pressure. The useful solution is the one another engineer can verify, diagnose and reverse safely.
Junior developer asks: “How do I know whether using browser network timings is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for using browser network timings: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
5. Profiling Angular rendering
Profiling Angular rendering matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Begin with a real scenario and the evidence currently available. Naming the observable symptom prevents the discussion from collapsing into framework preference.
Junior developer asks: “How do I know whether profiling angular rendering is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for profiling angular rendering: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
6. Finding change-detection pressure
Finding change-detection pressure matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Model the boundary before choosing a tool. Inputs, outputs, ownership and failure behaviour determine which implementation is appropriate.
Junior developer asks: “How do I know whether finding change-detection pressure is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for finding change-detection pressure: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
7. Auditing JavaScript bundle cost
Auditing JavaScript bundle cost matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Prefer a thin experiment over a broad assumption. A small representative measurement often resolves questions that documentation alone cannot answer.
Junior developer asks: “How do I know whether auditing JavaScript bundle cost is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for auditing JavaScript bundle cost: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
8. Inspecting API gateway and network time
Inspecting API gateway and network time matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Design for maintenance under pressure. The useful solution is the one another engineer can verify, diagnose and reverse safely.
Junior developer asks: “How do I know whether inspecting api gateway and network time is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for inspecting api gateway and network time: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
9. Tracing ASP.NET Core requests
Tracing ASP.NET Core requests matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Begin with a real scenario and the evidence currently available. Naming the observable symptom prevents the discussion from collapsing into framework preference.
Junior developer asks: “How do I know whether tracing ASP.NET Core requests is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for tracing ASP.NET Core requests: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
10. Detecting thread-pool starvation
Detecting thread-pool starvation matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Model the boundary before choosing a tool. Inputs, outputs, ownership and failure behaviour determine which implementation is appropriate.
Junior developer asks: “How do I know whether detecting thread-pool starvation is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for detecting thread-pool starvation: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
11. Measuring outbound HTTP calls
Measuring outbound HTTP calls matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Prefer a thin experiment over a broad assumption. A small representative measurement often resolves questions that documentation alone cannot answer.
Junior developer asks: “How do I know whether measuring outbound http calls is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for measuring outbound http calls: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
12. Finding allocation and GC pressure
Finding allocation and GC pressure matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Design for maintenance under pressure. The useful solution is the one another engineer can verify, diagnose and reverse safely.
Junior developer asks: “How do I know whether finding allocation and gc pressure is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for finding allocation and gc pressure: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
13. Inspecting EF Core query behaviour
Inspecting EF Core query behaviour matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Begin with a real scenario and the evidence currently available. Naming the observable symptom prevents the discussion from collapsing into framework preference.
Junior developer asks: “How do I know whether inspecting ef core query behaviour is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for inspecting ef core query behaviour: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
14. Detecting N+1 queries
Detecting N+1 queries matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Model the boundary before choosing a tool. Inputs, outputs, ownership and failure behaviour determine which implementation is appropriate.
Junior developer asks: “How do I know whether detecting n+1 queries is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for detecting n+1 queries: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
15. Capturing SQL execution plans
Capturing SQL execution plans matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Prefer a thin experiment over a broad assumption. A small representative measurement often resolves questions that documentation alone cannot answer.
Junior developer asks: “How do I know whether capturing sql execution plans is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for capturing sql execution plans: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
16. Reading logical reads and CPU time
Reading logical reads and CPU time matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Design for maintenance under pressure. The useful solution is the one another engineer can verify, diagnose and reverse safely.
Junior developer asks: “How do I know whether reading logical reads and cpu time is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for reading logical reads and cpu time: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
17. Checking indexes and statistics
Checking indexes and statistics matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Begin with a real scenario and the evidence currently available. Naming the observable symptom prevents the discussion from collapsing into framework preference.
Junior developer asks: “How do I know whether checking indexes and statistics is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for checking indexes and statistics: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
18. Identifying blocking and deadlocks
Identifying blocking and deadlocks matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Model the boundary before choosing a tool. Inputs, outputs, ownership and failure behaviour determine which implementation is appropriate.
Junior developer asks: “How do I know whether identifying blocking and deadlocks is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for identifying blocking and deadlocks: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
19. Testing connection-pool pressure
Testing connection-pool pressure matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Prefer a thin experiment over a broad assumption. A small representative measurement often resolves questions that documentation alone cannot answer.
Junior developer asks: “How do I know whether testing connection-pool pressure is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for testing connection-pool pressure: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
20. Reproducing with realistic data
Reproducing with realistic data matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Design for maintenance under pressure. The useful solution is the one another engineer can verify, diagnose and reverse safely.
Junior developer asks: “How do I know whether reproducing with realistic data is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for reproducing with realistic data: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
21. Load testing safely
Load testing safely matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Begin with a real scenario and the evidence currently available. Naming the observable symptom prevents the discussion from collapsing into framework preference.
Junior developer asks: “How do I know whether load testing safely is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for load testing safely: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
22. Prioritising by user impact
Prioritising by user impact matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Model the boundary before choosing a tool. Inputs, outputs, ownership and failure behaviour determine which implementation is appropriate.
Junior developer asks: “How do I know whether prioritising by user impact is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for prioritising by user impact: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
23. Verifying improvements without regression
Verifying improvements without regression matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Prefer a thin experiment over a broad assumption. A small representative measurement often resolves questions that documentation alone cannot answer.
Junior developer asks: “How do I know whether verifying improvements without regression is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for verifying improvements without regression: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
24. Creating a performance operating model
Creating a performance operating model matters in a distributed web application whose users report slowness without knowing which layer owns the delay. The desired result is a measured diagnosis that identifies the dominant constraint before the team spends time optimising. Design for maintenance under pressure. The useful solution is the one another engineer can verify, diagnose and reverse safely.
Junior developer asks: “How do I know whether creating a performance operating model is complete?”
Practical exercise
Choose one feature or incident from a system you know. Create a short evidence pack for creating a performance operating model: scenario, diagram, baseline, hypothesis, smallest experiment, failure matrix, test cases, observability and rollback. Ask a teammate to challenge what you have assumed. Update the design using the questions rather than merely defending the first idea.
Final Perspective
The chapters form a repeatable loop: state the outcome, gather evidence, model boundaries, test the smallest useful change, observe the real result and refine the next decision. Apply that loop selectively rather than treating the guide as ceremony. Its purpose is a measured diagnosis that identifies the dominant constraint before the team spends time optimising.
