DEV Community

Sahil Singh
Sahil Singh

Posted on • Originally published at glue.tools

Call Graphs That Prevent Production Incidents

The most expensive bugs aren't the ones caught in code review. They're the ones that pass code review because the reviewer didn't know about a dependency three layers deep.

The Pattern

  1. Engineer changes Function A
  2. Code reviewer checks Function A — looks good
  3. Function A is called by Service B (not in the PR diff)
  4. Service B passes the result to Handler C (also not in the diff)
  5. Handler C has a null check that assumed Function A's old return type
  6. Deploy to production
  7. Handler C crashes at 2 AM
  8. On-call engineer spends 4 hours tracing the issue back to the change in Function A

This isn't a code quality problem. The change to Function A was correct. The code review was thorough. The bug exists in the gap between what the reviewer can see (the diff) and what the system actually does (the full call graph).

What Call Graphs Show

A call graph maps every function-to-function relationship in your codebase:

authMiddleware.validateToken()
  → sessionService.getSession()
    → redisClient.get()
    → sessionStore.validate()
      → cryptoUtils.verifySignature()
  → userService.getPermissions()
    → permissionsCache.get()
    → database.query()
Enter fullscreen mode Exit fullscreen mode

This isn't just "what imports what." It's the runtime execution path — what actually gets called when this code runs.

Blast Radius Analysis

Before any code change, you should know:

  1. Direct callers: What functions call the function you're changing?
  2. Transitive callers: What calls those callers? Follow the chain up.
  3. Downstream effects: What does your function call, and what do those functions affect?
  4. Shared state: Does your function read or write state that other functions depend on?

The union of these is the "blast radius" — everything that could potentially be affected by your change.

How This Prevents Incidents

Before: Hope-Based Deployment

  1. Make a change
  2. Run the tests that exist (which may not cover transitive paths)
  3. Get code review from someone who knows this file (but maybe not downstream consumers)
  4. Deploy and hope

After: Informed Deployment

  1. Make a change
  2. Call graph shows: "This function is called by 7 services, 3 of which have null checks on the return value"
  3. Check those 3 null checks — discover one assumes non-null
  4. Fix the null check in the same PR
  5. Deploy with confidence

The difference: 10 minutes of call graph analysis vs. 4 hours of production incident response.

Real-World Examples

The Session Timeout Bug:
An engineer changed the session TTL from 30 minutes to 60 minutes. Seemed harmless. But the WebSocket service had a hardcoded 30-minute reconnection timer that assumed sessions would expire at 30 minutes. After 30 minutes, WebSocket connections would reconnect but find a valid session, creating duplicate connections. Memory usage climbed until the service OOMed.

Call graph would have shown: sessionConfig.TTL → read by sessionService.createSession() AND websocketService.reconnectTimer(). The dependency is visible.

The API Response Change:
An engineer added a field to an API response. Non-breaking change, right? Except a downstream consumer parsed the response and had a strict size check for caching. The larger response exceeded the cache size limit and started bypassing the cache. API latency increased 300%.

Call graph shows: apiController.getUser() → consumed by mobileApp.userProfile() AND cacheProxy.intercept(). The cache dependency is visible.

Building Effective Call Graphs

Not all call graphs are equal. Useful ones need:

  1. Cross-language support: Your API (TypeScript) calls your database (SQL) calls your cache (Redis). The graph should span all layers.
  2. Dynamic resolution: Method calls through interfaces and abstract classes need to resolve to concrete implementations.
  3. Depth control: Full transitive call graphs can be overwhelming. Show 2-3 levels by default, expandable on demand.
  4. Change-aware filtering: When reviewing a PR, show only the subgraph affected by the changed functions.
  5. Historical overlay: "The last time someone changed this function, which downstream consumers broke?" This combines call graphs with regression history.

This is what Glue builds during codebase indexing. Every function call, every dependency, every transitive path — mapped and queryable. So when you're about to change a function, you know exactly what you're affecting before you deploy.


Originally published on glue.tools. Glue is the pre-code intelligence platform — paste a ticket, get a battle plan.

Top comments (0)