mika's open learning notebook

field notes, essays, and things learned by building


Software Engineering

Code Tracing & Runtime Paths

A practical way to follow execution, values, callbacks, and state through an unfamiliar codebase without trying to understand everything at once.

Reading code is different from reading prose

A program is not really read top-to-bottom.

The important question is usually:

What actually runs, in what order, and what changes along the way?

That means code tracing is mostly about following execution rather than reading every file.

Start from a concrete entry point

Instead of opening the largest module and trying to understand everything, start from something observable.

Examples:

  • application startup
  • button click
  • CLI command
  • timer callback
  • file import
  • API request
  • animation transition

Then follow the runtime path.

user action
→ event handler
→ helper/service
→ state change
→ side effect
→ UI update

This creates a path through the codebase instead of a pile of files.

Track three things

When tracing code, I get more value from tracking three columns than from reading passively:

where am I?
what values exist here?
what changes next?

For example:

on_click()
    file_path = selected_path
    ↓
parse_file(file_path)
    records = [...]
    ↓
save_records(records)
    database changes
    ↓
refresh_ui()
    visible list changes

The important part is not memorizing every line. It is preserving the chain of cause and effect.

Trace values, not just functions

A call graph tells me which function calls another function.

A data-flow trace tells me what happened to the actual value.

Those are different questions.

raw_model = "ABC-123 "
→ strip()
→ "ABC-123"
→ normalize()
→ "abc-123"
→ lookup key

When a bug involves duplicates, stale state, parsing, or unexpected output, the value transformation is often more important than the function names.

Event-driven code needs a timeline

GUI applications are harder to trace because control returns to the event loop.

The path may look like:

click
→ callback starts
→ request async work
→ callback returns
→ event loop continues
→ signal/timer fires later
→ second callback resumes the feature

If I mentally flatten that into one synchronous function, I misunderstand the program.

For GTK, timers, animations, and asynchronous UI behavior, I need to mark where control leaves the current callback.

State changes deserve explicit notes

For stateful software, I should write the transition beside the function call.

Example:

handle_pickup()
state: IDLE → PICKUP

animation finishes
state: PICKUP → DRAGGED

mouse released
state: DRAGGED → PUT_DOWN

This is often enough to expose a bug where the visual animation changed but the authoritative state did not.

Use breakpoints and logs to verify the mental model

Reading gives me a hypothesis.

Runtime evidence tells me whether the hypothesis is true.

Useful evidence:

  • debugger breakpoints
  • temporary logging
  • stack traces
  • variable inspection
  • state transition logs
  • request/response logs

The point is not to log everything. It is to answer a specific tracing question.

Large codebases need boundaries

I do not need to understand the whole repository before making progress.

A better rule is:

entry point
→ immediate dependencies
→ state/data owner
→ side effect
→ return path

Then stop.

If another subsystem matters, trace into it deliberately.

Main lesson

Code tracing turns an unfamiliar codebase into a sequence of observable steps.

I do not need complete understanding first. I need a reliable path from trigger to consequence.

Still practicing

I want to get faster at tracing:

  • callback-heavy GUI code
  • inheritance/mixins
  • async code
  • dependency injection
  • code where state is mutated in several places

Next time

For any confusing feature, write this before changing code:

trigger:
entry function:
important input:
state before:
calls:
side effects:
state after:
visible result:

If I cannot fill that in, I probably do not understand the runtime path well enough to refactor it yet.