0 to 1

nums.size() vs int n = nums.size() in a For Loop: Which One Should You Actually Use?

You write this loop a hundred times a week and never think about it:

for (int i = 0; i < nums.size(); i++) {

}

It compiles. It passes your tests. It ships. Then, months later — or thirty seconds into a coding interview — someone feeds it an empty vector, and everything goes sideways. Not a crash you can point to on line 12. Just... your loop runs forever, or reads garbage memory, or returns a wrong answer nobody can explain.

Nine times out of ten, the culprit is one of these two lines quietly living somewhere nearby:

for (int i = 0; i < nums.size();       i++)   // fine, until...
for (int i = 0; i < nums.size() - 1;   i++)   // ...this shows up

Let's talk about why that second line is a landmine, what the "cache it first" habit actually buys you, and where it doesn't save you.

The Setup: Two Ways to Write the Same Loop

// Version A — ask the container every time
for (int i = 0; i < nums.size(); i++) { }

// Version B — ask once, remember the answer
int n = nums.size();
for (int i = 0; i < n; i++) { }

On a good day, these do exactly the same thing. The interesting part is what happens on a bad day.

The Landmine: Unsigned Arithmetic

Here's the detail that catches people off guard: nums.size() doesn't return a normal int. It returns size_t — an unsigned number. Unsigned numbers can't go negative. So when someone writes:

for (int i = 0; i < nums.size() - 1; i++) {
    // compare nums[i] and nums[i + 1]
}

...and nums happens to be empty, size() is 0. And 0 - 1, in unsigned math, isn't -1. There's no negative to go to, so it wraps all the way around to roughly 18 quintillion. Your loop condition just silently became i < 18446744073709551615, and your "obviously correct" comparison loop is now reading way past the end of the array.

This is the single most common reason a two-pointer or sliding-window solution passes every normal test case and then face-plants on the empty-input edge case — the one test everyone forgets to trace through by hand.

Where Caching Actually Helps

int n = nums.size();
for (int i = 0; i < n - 1; i++) {
    // n - 1 is a normal signed int subtraction — no wraparound, even when n == 0
}

Cache the size into an int, and this specific bug just... stops existing. n is signed, so n - 1 behaves the way your brain expects it to. It's not that caching is inherently "faster" or "more correct" in general — it's that this particular habit happens to force you through a signed integer, which is what actually kills the bug.

(You could get the same fix by writing (int)nums.size() - 1 inline, for what it's worth — but almost nobody does that. Caching is just the version people actually reach for.)

The Twist: Sometimes You Want the Live Version

Here's where it gets genuinely interesting instead of just being a "gotcha":

for (int i = 0; i < nums.size(); i++) {
    if (someCondition) nums.push_back(x);   // container is growing
}

If you cache n before this loop, new elements pushed in during the loop get silently ignored — the loop only ever sees the original count. If you want the loop to notice new elements as they're added, the inline version is correct and caching would be the bug.

So this isn't "always cache, no exceptions." It's: know whether your loop bound is supposed to be a fixed snapshot or a moving target, and write the code that matches that intent — instead of defaulting to whichever one you typed first out of habit.

Performance: The Argument Nobody Actually Needs to Have

Quick myth-bust, since it comes up: .size() is O(1). It's not strlen() scanning character by character — it's a stored counter. Calling it every iteration isn't "slow." With optimizations on, compilers frequently hoist it out of the loop themselves anyway. If someone tells you to cache .size() purely for speed, they're solving a problem that mostly doesn't exist. The real reason to care is the signed/unsigned trap above, plus:

The Takeaway

Both patterns are legitimate — the point was never "always cache" vs. "never cache." It's this: your loop bound is either a frozen snapshot or a live value, and those are two different decisions with two different failure modes. The bug isn't caused by picking the "wrong" one — it's caused by not realizing you were making a choice at all, and getting bitten the one time your input happens to be empty.

Trace your loop through n = 0 by hand once. That's the whole habit.