5 exercises — reading trace waterfalls, finding the critical path, spotting N+1 service calls, span.kind metadata, and head-based vs. tail-based sampling.
0 / 10 completed
1 / 10
Opening a slow request in Jaeger, you see a waterfall view: api-gateway [====================================] 2004ms auth-service [==] 42ms orders-service [========================================] 1920ms orders-db [=======================================] 1890ms
Where should you focus your investigation, and why?
Reading a trace waterfall is about finding where time is actually spent, not which span is drawn widest at the top level. The api-gateway span's total duration (2004ms) is really just the sum of the time its downstream calls take, plus its own small overhead — it's a parent span containing child spans.
How to read the indentation: each level of indentation is a child of the span above it. orders-db is a child of orders-service, which is a child of api-gateway. To find the bottleneck, look for the deepest span whose duration is close to its parent's duration — that's the span doing the actual work, versus spans that are just waiting on their children.
Here, orders-db (1890ms) is almost exactly as long as its parent orders-service (1920ms), meaning the database call is essentially the entire cost of that branch. This tells you precisely where to look next: the SQL query itself (check EXPLAIN ANALYZE), not the network, not authentication, not the gateway.
2 / 10
A trace shows three sibling spans under checkout-service: checkout-service [====================] 500ms inventory-check [==========] 250ms pricing-service [========] 200ms fraud-check [====================] 500ms
How would you describe this structure to a teammate, and where is the optimisation opportunity?
Recognising parallel (fan-out) spans versus sequential spans is one of the highest-value trace-reading skills. The visual cue in most trace UIs (Jaeger, Zipkin, Tempo) is that sibling spans overlap horizontally in time rather than appearing one after another — the parent's total duration equals the longest overlapping child, not the sum of all children.
Critical path vocabulary: the critical path of a trace is the specific chain of spans that determines the total end-to-end duration. In a fan-out, only the slowest parallel branch is on the critical path — the faster siblings finish "for free" within that window. This has a direct engineering consequence: teams sometimes waste effort optimising a span that looks slow in isolation (200ms feels slow) but isn't actually contributing to overall latency, while the real bottleneck (fraud-check) goes unaddressed.
Communicating this precisely — "fraud-check is the critical path here, the other two are parallel and don't matter" — focuses the team's optimisation effort correctly instead of chasing the wrong span.
3 / 10
You're comparing two traces for the same endpoint. Trace A (fast, 45ms) has 3 spans. Trace B (slow, 3100ms) has 47 spans, many of them tiny (<1ms) calls to the same user-preferences service. What problem does Trace B illustrate?
This is one of the most common patterns distributed tracing reveals that logs alone often hide: "death by a thousand small calls." Each individual span looks harmless (sub-millisecond), but the trace waterfall exposes that they add up — especially because each network round-trip carries fixed overhead (connection handling, serialization, TLS) regardless of how little data it carries.
Why this is hard to spot without tracing: in logs alone, you'd see 40+ short, successful entries with no obvious individual problem. The trace view, by contrast, visually shows dozens of thin bars stacked sequentially — an unmistakable pattern once you've learned to recognise it.
The N+1 fix, in this context: replace the loop of per-item calls with a single batched request (e.g. getPreferences(userIds: [...]) instead of calling getPreference(userId) once per user). This is directly analogous to the classic N+1 query problem in ORMs, just at the service-call level instead of the database level — the same diagnostic instinct ("why are there so many near-identical small operations?") applies.
4 / 10
A span in a trace has this metadata attached: span.kind: client http.status_code: 502 error: true otel.status_description: "upstream connect error or disconnect/reset before headers"
How would you interpret this span's error, and what does span.kind: client tell you?
span.kind is standard OpenTelemetry metadata describing the span's role: client (this service calling out to another), server (this service handling an incoming request), producer/consumer (async messaging), or internal (in-process work). Reading span.kind correctly tells you which side of a call a given span represents — essential when tracing across a service mesh where both the caller's "client span" and the callee's "server span" for the same logical call appear in the trace.
"upstream connect error or disconnect/reset before headers" is a common Envoy/service-mesh error message meaning the proxy attempted to connect to the downstream service but the connection failed or was reset before any HTTP response headers came back — this points at the downstream service being unreachable, crashed, or overloaded, rather than a problem with the request itself.
Practical use: when triaging a multi-service trace with several error spans, checking span.kind and following the error to its deepest occurrence (the true origin, not just where the error was first observed by an upstream caller) is the standard method for finding root cause rather than the first symptom.
5 / 10
An engineer says in a stand-up: "I looked at the trace for the timeout ticket — turns out it's 100% sampling bias. We only capture 1% of traces, and none of the sampled ones happened to hit the slow path." What is this engineer describing, and what's a better approach?
Sampling exists because capturing 100% of traces in a high-traffic system is expensive (storage, processing, network overhead), so most systems only keep a fraction.
Head-based sampling — the decision to sample is made at the start of the trace (e.g. "randomly record 1% of all requests"), independent of what happens during the request. This is simple and cheap, but has exactly the blind spot described here: a rare slow or failing request has only a 1% chance of being captured, so investigating "why was request X slow" after the fact often turns up nothing, because that specific request was never sampled.
Tail-based sampling — spans are buffered temporarily (often by a collector like the OpenTelemetry Collector) and the sampling decision is deferred until the full trace is available, so rules like "always keep traces with errors" or "always keep traces over 1 second" can be applied. This guarantees the interesting traces are never missed, at the cost of more buffering infrastructure.
Explaining this trade-off clearly — "we're missing the failure because of head-based sampling, we should switch to tail-based sampling for error/latency traces" — turns a vague "tracing isn't helping" complaint into a specific, actionable infrastructure change.
6 / 10
During a code review, Sarah mentions that the trace for user profile updates is consistently slow. The trace shows several spans within the `user-service` calling external APIs. Which aspect of the trace data should you immediately investigate to understand potential bottlenecks?
Focusing on the latency of external API calls is crucial because these are often the primary source of slow traces. The number of spans originating from `user-service` might indicate excessive branching, but API call latency is usually the first thing to investigate when a trace shows overall slowness. Error rates can highlight problems, but don't directly explain *why* something is slow; it's about the time taken.
7 / 10
You've received a Slack message from a colleague: 'The trace for order placement shows a huge spike in latency – around 3 seconds! It's bouncing between the `payment-service` and `inventory-service`. I suspect there might be a database issue.' What immediate action should you take based on this information?
Analyzing the trace is the most effective first step because it will pinpoint exactly which spans within `payment-service` and `inventory-service` are causing the delay. Scaling resources without understanding the root cause could be a waste of effort. While database issues are possible, the trace data provides more specific evidence to guide your investigation.
8 / 10
You're reviewing a PR that adds logging to a microservice. The PR description includes this: 'Added detailed logs for all requests to the API gateway, including timestamps and correlation IDs.' What is the primary benefit of adding these logs in relation to distributed tracing?
Correlation IDs are key – they allow you to link spans across different services into a single trace. Without this, logs would be useless for understanding how requests flow through your system and identifying performance bottlenecks. The other options represent incorrect assumptions about log data's role in tracing.
9 / 10
A trace shows a span with the following metadata: span.kind: server, error: true, otel.status_code: 500. What does this likely indicate?
The key indicators are `span.kind: server` (meaning this is a backend process), `error: true`, and `otel.status_code: 500`. These combined suggest that the service itself experienced an error, resulting in a 500 status code – a common HTTP error indicating a problem on the server-side.
10 / 10
During a daily stand-up, a developer says: 'I've been looking at traces for our new recommendation engine. I'm seeing a lot of short spans – some less than 5ms – constantly firing off to the user preferences service. It's making the overall trace look really bloated.' What is this developer primarily concerned about?
This developer is rightly concerned about 'span bloat'. Many small, rapid-fire spans can obscure the true performance characteristics of the system and make it harder to identify genuine bottlenecks. While other factors could be involved, frequent short spans are a common cause of trace complexity.
What will I practise in "Distributed Trace Reading — Log Reading Exercises"?
Practice reading Jaeger and Zipkin trace waterfalls: identifying the critical path, fan-out patterns, N+1 service calls, span kinds, and sampling bias. 5 exercises for engineers working with distributed tracing.
How many exercises are in this module?
This module has 10 multiple-choice exercises, each with instant feedback and a full explanation of the correct answer.
Is this exercise free to use?
Yes. Every exercise on CoderSlingo, including this one, is free to use with no account, sign-up, or paywall.
Do I need to create an account to do these exercises?
No account is required. Just click an option to answer — your score for this session is tracked automatically in the progress bar above.
What happens if I choose the wrong answer?
You'll immediately see which answer was correct, plus a full explanation covering the vocabulary and reasoning behind it — mistakes are where most of the learning happens.
Can I retry the exercises if I want a higher score?
Yes — use the "Try again" button on the results screen to reset and go through all the questions again.
Is my progress saved if I close the page?
No. Progress is tracked only for your current visit; reloading or leaving the page resets the counter. This keeps the exercise simple and account-free.
Where can I find more Log Reading exercises?
Browse the full Log Reading hub for related drills, or check the "Next up" link below to continue with a connected topic.
How is this different from reading an article on the same topic?
Articles explain vocabulary and concepts in prose; this exercise tests and reinforces that vocabulary through active recall with immediate feedback — the two work best together.
Who writes these exercises?
Every exercise is written by the CoderSlingo team, drawing on real workplace English used in IT roles, then reviewed for accuracy and clarity.