Back to Stream Practice: Collections & Numbers
Q8Question 8 of 11

Predict output: loop adding to List<Short> with removal: autoboxing trap

Key Concepts
  • List.remove(int) removes by INDEX, List.remove(Object) removes by VALUE
  • When the argument is a primitive int or short widened to int, Java calls remove(int index), not remove(Object)
  • To remove by value, explicitly box the argument: list.remove(Short.valueOf(i)) or list.remove((Integer) i)
  • This is an autoboxing overload resolution trap -- one of the most common Java interview puzzles
  • removeIf(predicate) is the safest way to remove by value because it avoids the overload ambiguity
  • The issue applies to List<Integer>, List<Short>, and List<Long> -- any boxed numeric wrapper

Definition

List.remove(int) removes by index, List.remove(Object) removes by value. When the argument is a primitive int, Java picks the index overload, causing unexpected behavior with numeric wrapper lists.
  • remove(int index) removes the element at the specified position in the list.
  • remove(Object o) removes the first occurrence of the specified element by value.
  • When the argument is a primitive int or short (widened to int), Java resolves to remove(int index) because an exact int overload exists.
  • Autoboxing to Short/Integer does NOT happen during overload resolution when an int overload exists.
  • To force value removal, explicitly box: list.remove(Short.valueOf(i)) or cast: list.remove((Short) (short) i).
  • The safest alternative is removeIf(x -> x == target) which has no overload ambiguity.

You add cheque numbers 1, 2, 3 to a box. You say "remove 1". Did you mean remove cheque number 1 (by value) or remove the cheque at position 1 (by index)? Java picks "by index" when the argument is int, and "by value" when it is a Short object. This ambiguity causes the surprise.

Understanding the Concepts

What is the autoboxing trap?

  • List (and List) has two remove methods:
  • remove(int index) removes by position.
  • remove(Object o) removes by value.

When you call list.remove(1), the literal 1 is an int. Java picks remove(int index) because an exact int overload exists. It does NOT autobox 1 to Short or Integer.

How does the trap manifest?

JAVA
package com.bank.util;

import java.util.ArrayList;
import java.util.List;

public class AutoboxingTrapDemo {
    public static void main(String[] args) {
        List<Short> list = new ArrayList<>();

        for (short i = 0; i < 5; i++) {
            list.add(i);  // autoboxes short to Short
            if (list.size() > 1) {
                list.remove(i);  // TRAP: i is widened to int!
                // This calls remove(int index), NOT remove(Short value)
            }
        }

        System.out.println(list);
    }
}

The remove(i) call removes the element AT INDEX i, not the element WITH VALUE i. This causes IndexOutOfBoundsException when i exceeds the list size.

How do you fix it?

JAVA
// CORRECT: remove by VALUE using explicit boxing
list.remove(Short.valueOf(i));       // remove the Short with value i

// CORRECT: remove by VALUE using cast
list.remove((Short) i);              // cast short to Short wrapper

// SAFEST: use removeIf -- no overload ambiguity
short target = i;
list.removeIf(x -> x == target);     // removes elements matching value

How do you demonstrate the difference?

JAVA
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30, 40));

// Remove by INDEX: removes element at position 1 (which is 20)
nums.remove(1);           // [10, 30, 40]

// Remove by VALUE: removes the Integer object 30
nums.remove(Integer.valueOf(30));  // [10, 40]

What are the overload resolution rules?

| Argument Type | Method Called | Reason |
|---------------|-------------|--------|
| int literal (1) | remove(int index) | Exact match for int parameter |
| short variable | remove(int index) | short widens to int (widening > boxing) |
| Short object | remove(Object o) | Short is an Object |
| Integer.valueOf(1) | remove(Object o) | Integer is an Object |

Java's overload resolution prefers widening over boxing. A short widens to int, matching remove(int index), rather than boxing to Short to match remove(Object).

What are common mistakes?

WRONG: Assuming list.remove(i) removes by value when i is int.

WRONG: Not realizing that short widens to int before overload resolution.

What should a senior engineer know?

  • This trap is specific to List because List inherits remove(int index) from java.util.List AND remove(Object) from java.util.Collection. The two overloads create the ambiguity.
  • Set.remove(Object) has only one overload, so this trap does not apply to Sets.
  • In production banking code, prefer removeIf(predicate) for value-based removal. It is clear, safe, and avoids the overload trap entirely.
  • This is one of the top 5 Java interview trick questions alongside == vs equals, String pool, finally with return, and ConcurrentModificationException.

Visual Overview

DIAGRAM
  List<Integer> nums = [10, 20, 30, 40]

  nums.remove(1)
  ┌─────────────────────────────────────┐
  │ Argument: int 1                     │
  │ Match: remove(int index)            │
  │ Action: remove element at index 1   │
  │ Removed: 20                         │
  │ Result: [10, 30, 40]                │
  └─────────────────────────────────────┘

  nums.remove(Integer.valueOf(30))
  ┌─────────────────────────────────────┐
  │ Argument: Integer 30                │
  │ Match: remove(Object o)             │
  │ Action: remove first occurrence of 30│
  │ Removed: 30                         │
  │ Result: [10, 40]                    │
  └─────────────────────────────────────┘

Key Takeaway

The autoboxing trap occurs because List has two remove overloads: remove(int index) for position-based removal and remove(Object) for value-based removal. When the argument is a primitive int or short (widened to int), Java calls the index overload because overload resolution prefers widening over boxing.

To remove by value, explicitly box the argument: list.remove(Short.valueOf(i)) or use removeIf(x -> x == target) which has no overload ambiguity. The removeIf approach is the safest and most readable.

At National Bank of India, a list of short settlement codes is pruned in a loop. Calling remove(code) where code is a primitive short (widened to int) silently deletes the wrong element and corrupts the batch. Using removeIf prevents this bug.

Remember one sentence: remove(int) is by index, remove(Object) is by value, and Java picks by-index when the argument is a primitive int.

---

Share
Use ← → arrow keys to navigate