← Table of Contents

Rubber Duck Debugging / 5 of 5 / 1 dilihat

Variable Tracing

Variable Tracing: Tracking the Real Value, Not the Assumed One

One of the most common traps in debugging is trusting a variable's value based on what the code's logic "should" produce, without ever actually verifying its real value at runtime.

A Case Study: Pagination Off by One

Take this code:

var currentPage = 0

fun loadMore() {
currentPage++
getData(currentPage)
}
```

Ask "what page does the first request use?" and plenty of people will say "0," since that's the initial value. But trace the execution order line by line:

Value of currentPage before loadMore() is called: 0

Line executed: currentPage++
Value of currentPage now: 1

Line executed: getData(currentPage)
Value passed to getData: 1
```

The first request actually sent uses page=1, not page=0. A mistake this small — not noticing that currentPage++ runs before getData is called — can send a developer chasing a backend issue that doesn't exist, when the real problem is the increment order on the Android side.

The Variable Tracing Technique

The simplest and most reliable approach is recording a variable's value at every important point, before and after any operation that changes it:

[Before] currentPage = 0
[Operation] currentPage++
[After] currentPage = 1

This sounds trivial, but that's exactly the point — it forces you to stop guessing and start verifying. For cases involving several variables at once, log all the relevant values on one line so the order is easy to follow:

Log.d("PAGINATION", "before: currentPage=$currentPage")
currentPage++
Log.d("PAGINATION", "after: currentPage=$currentPage, requesting page=$currentPage")

Why This Is More Than Just "Debugging"

Variable tracing is different from just dropping a breakpoint and glancing at a value. Tracing means recording a variable's history of changes across the execution flow, not just a snapshot at one point in time. That distinction matters when a variable gets modified from multiple places — for instance, a currentPage that can be changed by loadMore(), by a refresh process, and by a reset triggered on pull-to-refresh.

An Example From Experience

There was a case where content display duration on a signage screen would occasionally be off by a few seconds. The initial assumption was a bug in the timer logic. But tracing the duration variable's value at every point — when the playlist loaded, when content started displaying, when content switched — showed the value was correct throughout the normal flow. The problem only showed up under a specific condition: right after a device reconnected following a network drop, one duration variable would briefly get reset to a default value before it was actually reloaded from the latest configuration, and content rendering would run on that default value for a few hundred milliseconds. Without explicitly recording variable values at these critical points, this kind of thing is nearly impossible to catch just by reading the code.

Common Mistakes

  • Changing code based on an assumed value — like tacking on a -1 or +1 somewhere because it "seems like an off-by-one," without ever verifying the actual runtime value.
  • Only checking a variable's value at one point — the same variable can change several times across the flow, and the point you happened to check might not be the relevant one.
  • Ignoring line execution order — as in the currentPage++ example above, the order lines run in determines what value actually gets used.

Once a variable's value has been traced and you've found the point where it starts diverging from what's expected, the next step is often examining the condition (if-else) logic that branches based on that value — which is what the next article covers.