JavaScript Array Pitfalls: Why nums[-1] Doesn't Break Your 3Sum Solution

Discover why nums[-1] doesn't crash JavaScript and how understanding arrays as objects can help you debug algorithm problems like 3Sum. Learn about negative indexing, property access patterns, and JavaScript's unique array behavior.


When solving algorithm problems like 3Sum, we often focus on pointer movement and duplicate handling. But in JavaScript, subtle quirks in how arrays behave as objects can trip you up. Let's walk through a real debugging journey that exposes these hidden pitfalls.

The Complete 3Sum Solution

Here's a typical 3Sum implementation that demonstrates the behavior we'll explore:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
  const res = [];
  nums.sort((a, b) => a - b);
 
  for (let i = 0; i < nums.length; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    if (nums[i] > 0) break;
 
    let l = i + 1, r = nums.length - 1;
    while (l < r) {
      const sum = nums[i] + nums[l] + nums[r];
      if (sum > 0) r--;
      else if (sum < 0) l++;
      else {
        res.push([nums[i], nums[l], nums[r]]);
        while (l < r && nums[l] === nums[l + 1]) l++;
        while (l < r && nums[r] === nums[r - 1]) r--;
        l++; r--;
      }
    }
  }
  return res;
};

For more context on the 3Sum problem and optimization techniques, check out our comprehensive guide on sum problems from 2Sum to KSum.

The Dangerous-Looking Duplicate Check

Notice this line in the solution above:

if (i > 0 && nums[i] === nums[i - 1]) continue;

But what if we wrote it like this instead?

for (let i = 0; i < nums.length; i++) {
  if (nums[i] === nums[i - 1]) continue; // skip duplicates
  // ... rest of algorithm
}

At first glance, this looks dangerous:

  • When i === 0, we access nums[-1]
  • In many languages, this would throw an array bounds error

But in JavaScript, it's actually fine. Why? Because nums[-1] doesn't throw—it evaluates to undefined. So nums[0] === nums[-1] becomes 1 === undefined, which is false. The loop continues without issue.

Still, the clearer, conventional way is:

if (i > 0 && nums[i] === nums[i - 1]) continue;

This makes the intent obvious: "skip duplicates after the first element."

Arrays Are Special Objects

Why does nums[-1] not blow up? Because arrays in JavaScript are just objects with special handling for non-negative integer keys.

Under the hood:

const nums = [1, 2, 3, 4];

is roughly equivalent to:

{
  "0": 1,
  "1": 2,
  "2": 3,
  "3": 4,
  length: 4,
  __proto__: Array.prototype
}

So when you write nums[-1], JavaScript simply looks for the property with key "-1". It doesn't exist → returns undefined. No runtime error.

This behavior is quite different from other array-related pitfalls you might encounter. For more on JavaScript array initialization issues, see our guide on common array pitfalls.

Assigning to Negative Indices

You can even do this:

const nums = [1, 2, 3, 4];
nums[-1] = 0;
 
console.log(nums); // [1, 2, 3, 4, -1: 0]
console.log(nums[-1]); // 0
console.log(nums.length); // 4 (unchanged)

Here "-1" becomes a normal object property, not an array index. That's why array methods (map, forEach, push) ignore it—they only operate on valid array indices.

Dot vs Bracket Notation

Why can't we do nums.-1? Because in JavaScript, dot notation only works with valid identifiers.

  • ✅ Valid: user.name, user.first_name, user.$dollar, user.var123
  • ❌ Invalid: user.123, user.-1, user.foo-bar, user.for

So:

nums[-1]    // ✅ works, equivalent to nums["-1"]
nums["-1"]  // ✅ works
nums.-1     // ❌ SyntaxError

If the property name isn't a valid identifier, you must use bracket notation.

Serialization and Hidden Properties

"Fake" keys like "-1" or "foo" don't appear in JSON:

const nums = [1, 2, 3];
nums[-1] = 99;
nums.foo = "bar";
 
console.log(JSON.stringify(nums)); // "[1,2,3]"
console.log(Object.keys(nums)); // ["0","1","2","-1","foo"]

These properties live on the array object, but they're invisible to normal array behavior and serialization.

Key Takeaways for Algorithm Problems

  1. nums[-1] is not the last element—it's just a string property lookup that returns undefined if not set. Safe to use in if (nums[i] === nums[i-1]), but clearer with an explicit i > 0 check.

  2. Arrays are objects. Non-negative integers are treated as real indices; everything else is just a property.

  3. Dot notation only works with valid identifiers. Use bracket notation otherwise.

  4. For the last element, use:

    nums[nums.length - 1]; // classic
    nums.at(-1); // modern ES2022+
  5. When debugging algorithm problems, remember: JavaScript array behavior can be subtly different from languages like Python or Java.

The 3Sum skip-duplicate trick exposed a deeper truth—arrays in JavaScript are just objects. Once you understand that, you'll avoid nasty surprises in both algorithms and production code.

For more algorithm problem-solving techniques, explore our guides on two-pointer methods and comprehensive sum problem solutions.