← Back to Insights

Debugging workflows

Debugging a System You Did Not Build

Written by:Better SoftwareBetter Software TeamUpdated 6 min read
Debugging a System You Did Not Build

Quick Answer

Debugging an unfamiliar system works best as a hypothesis-driven process rather than random exploration. Start with external symptoms and a testable hypothesis, check system health and recent changes, reproduce the issue in a simplified form, read logs and data stores systematically, add temporary instrumentation, and test one hypothesis at a time. Escalate with documented findings when stuck. Grepping every log unfiltered and restarting services at random are the common detours.

Debugging unfamiliar systems requires a structured, hypothesis-driven methodology. Begin by observing external symptoms and formulating falsifiable hypotheses. Systematically eliminate components and gather data to narrow down the problem space. This approach minimizes cognitive load and avoids random exploration.

Why This Happens

Engineers often face systems with undocumented behavior or opaque internal states. Lack of context prevents immediate identification of failure points. Systems built by others may have non-standard error handling or logging. Dependencies between services complicate pinpointing fault domains. Time pressure often leads to chaotic, unstructured attempts. Cognitive biases encourage chasing initial false leads. Without a clear process, investigation consumes excessive resources. Common root causes often recur across different systems, but initial diagnosis varies.

Investigation Process

  1. External Observation and Hypothesis Formation. Note all observable symptoms. Identify affected components and user impact. Formulate an initial hypothesis about the root cause. This hypothesis must be testable.
  2. Verify System Health Indicators. Check dashboards and monitoring alerts. Identify any saturated resources or failing services. Confirm system components are generally online.
  3. Review Recent Changes. Ascertain recent deployments, configuration changes, or dependency updates. Correlate changes with incident start times.
  4. Isolate and Simplify. Attempt to reproduce the issue in a controlled environment. Remove non-essential variables or inputs. Aim for the simplest reproduction case.
  5. Examine Logs Systematically. Start with high-level service logs. Filter by component, time, and relevant identifiers. Look for errors, warnings, or unexpected patterns. Expand to lower-level logs as needed.
  6. Inspect Key Data Stores. Query databases, caches, or message queues. Verify data integrity and expected state. Compare observed state against documented expectations. Debugging discrepancies between logs and database state is a common requirement.
  7. Instrument and Observe. Add temporary logging or tracing. Deploy changes to gather more specific data. Monitor resource utilization during problem reproduction.
  8. Test Hypotheses. Devise specific experiments to prove or disprove hypotheses. Each test should yield a clear result. Refine or discard hypotheses based on findings.
  9. Consult Documentation and Code. Review design documents, runbooks, or source code. Understand component interactions and data flows. This provides crucial context for unfamiliar systems.
  10. Escalate with Context. If stuck, summarize findings and unproven hypotheses. Present collected evidence to a team member with more context.

Practical Example

A new payment service started reporting intermittent transaction failures. This service was written in an unfamiliar language and deployed to a new cloud environment. No direct error messages appeared in the main application logs. The team followed the structured debugging process. First, they observed that failures occurred only for specific payment methods during peak load. Their initial hypothesis was a concurrency issue in the payment gateway integration. They checked their monitoring system; CPU and memory for the payment service hosts were normal. Database connection pools were not exhausted. Recent changes included a minor update to the payment_status asynchronous callback handler. Analyzing the new callback handler's logs revealed network timeouts when connecting to the third-party payment gateway. These timeouts happened under load. Further investigation showed the handler used a default, non-tuned HTTP client. The client lacked proper connection pooling and retry mechanisms. The team configured the HTTP client with appropriate timeouts and retries. The issue resolved after deployment.

Preventing Recurrence

Implement comprehensive monitoring for all new services. Include application-level metrics, such as transaction success rates and latency. Ensure thorough logging at appropriate verbosity levels. Establish clear runbooks for critical services. Automate common diagnostic checks into health endpoints. Conduct peer reviews of architecture and code for new systems. Prioritize robust error handling and observability from inception. Document system boundaries and external dependencies. A distributed system debugging workflow can prevent such issues from being isolated to one component.

What Teams Usually Do Instead

Many teams immediately grep all logs without filtering. This generates noise and wastes time. They might restart services randomly hoping the problem resolves itself instead of identifying root cause. Some dive directly into source code without understanding the system's external behavior. This leads to tunnel vision and missed systemic issues. Others blame external dependencies without evidence. They might escalate prematurely without providing adequate context or data. These unscientific approaches prolong incident resolution. They also fail to identify underlying systemic weaknesses. Unstructured debugging creates significant engineering toil and stress. It reduces overall team productivity by increasing interruptions.

Key Takeaways

  • Start with external symptoms, form hypotheses.
  • Systematically eliminate variables and narrow scope.
  • Prioritize recent changes and core system health.
  • Use logs and metrics to gather objective evidence.
  • Document findings and consult code for unfamiliar parts.

See also how we help teams build products.

Frequently Asked Questions

How Do You Debug a System You Do Not Know?

We use a structured, hypothesis-driven method. We note the external symptoms, form a testable hypothesis, check health indicators and recent changes, simplify the reproduction, examine logs and data stores, and test hypotheses one at a time, which limits cognitive load and avoids random exploration.

Why Is Debugging Unfamiliar Systems Hard?

We usually face undocumented behavior or opaque internal state, non-standard error handling or logging, and dependencies between services that make fault domains hard to pinpoint. Time pressure then pushes people toward chaotic attempts, and cognitive biases encourage chasing initial false leads.

What Should We Check First?

We check dashboards and monitoring alerts for saturated resources or failing services, then review recent deployments, configuration changes and dependency updates, and correlate them with the incident start time. Those checks quickly narrow where to look before anyone digs into logs or source code.

When Should We Escalate?

We escalate when we are stuck, but with context. We summarize the findings and the unproven hypotheses, then present the collected evidence to a team member who knows the system better. Escalating prematurely without data prolongs resolution and wastes the other person's time.

What Do Teams Usually Do Instead?

We often see teams grep all logs without filtering, restart services at random, dive into source code without understanding external behavior, or blame external dependencies without evidence. These unscientific approaches prolong incidents, create engineering toil and stress, and leave the underlying systemic weaknesses unidentified.

About the author

Better Software

Better Software Team

Product and engineering team

Better Software Team is the product and engineering team at Better Software. We build new software products and improve existing applications and systems, with deeper experience in healthcare, finance and energy.