List remove(int index) Method in Java with Examples

I still remember the first time a production bug slipped past my tests because I removed the wrong item from a list. The code “worked,” but it removed the element by value instead of by position. A simple user action moved the list order, and suddenly the wrong record vanished. That moment taught me a lasting lesson: list removal is deceptively simple, and you need to be precise about which overload you call and why. If you work in Java, you will eventually reach for remove(int index) on a List, and you should know exactly what it does, how it shifts elements, how it behaves on different list implementations, and what it costs you in time and safety.

Here, I walk you through the remove(int index) method with runnable examples, real-world scenarios, and a few patterns I rely on in modern Java projects. I’ll cover the method signature, index rules, performance trade‑offs, and the subtle edge cases that cause the most surprises. You will leave with a clear mental model and practical guidelines you can apply today.

The mental model I use for remove(int index)

remove(int index) does two things in one step: it removes the element at the given index and returns that element. It then shifts every element to the right of the removed position one slot to the left, shrinking the list by one. The list’s size changes immediately, and all later indices move.

When I explain this to juniors, I use the “index cards on a table” analogy. Imagine each element is a card laid out in order. When you lift one card, all the cards to the right slide left to close the gap. The order stays intact, but the positions change. That sliding effect is the source of most off‑by‑one bugs, so keep it front‑of‑mind.

Signature:

E remove(int index)

  • E is the list’s element type
  • index is the zero-based position to remove
  • return value is the removed element

If you pass an invalid index, the method throws IndexOutOfBoundsException.

A tight, runnable example that shows the shift

This first example is small enough to reason about line by line, and it shows the returned value along with the shifted list.

import java.util.ArrayList;

import java.util.List;

public class RemoveByIndexDemo {

public static void main(String[] args) {

List numbers = new ArrayList();

numbers.add(1);

numbers.add(2);

numbers.add(3);

numbers.add(4);

int indexToRemove = 2; // zero-based

Integer removed = numbers.remove(indexToRemove);

System.out.println("Removed: " + removed);

System.out.println("Remaining: " + numbers);

}

}

Expected output:

Removed: 3

Remaining: [1, 2, 4]

I like this example because it forces you to track the shift. If you remove index 2 (the third element), everything after it moves left by one. That’s exactly what happens in every list implementation that preserves order.

The method contract, in plain language

I keep the contract in my head as three statements:

  • You give a valid index.
  • You receive the element that was at that index.
  • Every element after that index moves one step left.

The contract is simple, but the impact depends on the list implementation. ArrayList shifts elements by copying array slots. LinkedList relinks nodes without array copies, but still traverses to find the index. CopyOnWriteArrayList creates a new backing array. So the behavior is consistent, but the cost is not. I’ll show that cost later.

Example: removing in a real workflow list

Most lists in production represent something meaningful: user tasks, queued events, items in a cart. I recommend writing examples that mirror that reality.

import java.util.ArrayList;

import java.util.List;

public class TaskQueueExample {

public static void main(String[] args) {

List queue = new ArrayList();

queue.add("Validate order");

queue.add("Reserve inventory");

queue.add("Charge payment");

queue.add("Generate invoice");

queue.add("Send confirmation email");

int stepToRemove = 2; // "Charge payment" is at index 2

String removed = queue.remove(stepToRemove);

System.out.println("Removed step: " + removed);

System.out.println("Updated queue: " + queue);

}

}

Output:

Removed step: Charge payment

Updated queue: [Validate order, Reserve inventory, Generate invoice, Send confirmation email]

Notice how the order stays intact. That’s a key property of List: it preserves insertion order, and remove(int index) preserves that order while closing the gap.

Indexing rules and the most common mistake

The most common mistake I see is accidentally calling the wrong overload on List. There are two remove methods in the List interface:

  • E remove(int index)
  • boolean remove(Object o)

With List, remove(2) can mean two different things. Because 2 is an int, Java selects remove(int index) and removes the element at position 2, not the element whose value is 2. That subtlety has caused more bugs than I care to count.

Here’s the safe, explicit pattern I use:

import java.util.ArrayList;

import java.util.List;

public class RemoveOverloadExample {

public static void main(String[] args) {

List ids = new ArrayList();

ids.add(101);

ids.add(102);

ids.add(103);

ids.add(104);

// Remove by index

ids.remove(1); // removes 102

// Remove by value (explicitly box)

ids.remove(Integer.valueOf(103));

System.out.println(ids);

}

}

Output:

[101, 104]

I recommend you always be explicit when removing by value from a List. If you intend to remove the number 2, call remove(Integer.valueOf(2)) or use a variable of type Integer. That removes ambiguity and makes code reviews faster.

Index validation patterns I actually use

remove(int index) throws IndexOutOfBoundsException if the index is negative or greater than or equal to the list size. I often validate index values when they come from external input (UI position, API payload, CSV row number). I do it with a tiny helper method.

import java.util.List;

public class SafeRemove {

public static E removeAt(List list, int index) {

if (index = list.size()) {

throw new IllegalArgumentException(

"Index " + index + " out of bounds for size " + list.size());

}

return list.remove(index);

}

}

I prefer throwing IllegalArgumentException here because it communicates a caller error clearly. In internal code, I might let IndexOutOfBoundsException bubble up. In public APIs, I convert it to something more explicit.

Removing in loops without surprises

Another classic pitfall is removing items from a list while iterating forward by index. The shift causes you to skip elements.

Problematic pattern:

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

if (shouldRemove(list.get(i))) {

list.remove(i);

}

}

If two removable elements are adjacent, the second one moves into the first one’s index after you remove the first, and then your loop increments i, skipping it.

I typically pick one of these solutions:

1) Iterate backwards by index:

for (int i = list.size() - 1; i >= 0; i--) {

if (shouldRemove(list.get(i))) {

list.remove(i);

}

}

2) Use an Iterator and its remove method:

import java.util.Iterator;

Iterator it = list.iterator();

while (it.hasNext()) {

String value = it.next();

if (shouldRemove(value)) {

it.remove();

}

}

If I need the removed element itself, I stick to the backward index loop, because Iterator.remove() doesn’t return the removed element.

When to use remove(int index) vs other approaches

I treat remove(int index) as the right tool when the list is a true ordered sequence and you care about the position. If the list is really a set of distinct values and you care about membership, a Set might be a better fit. If you need random access and frequent removal in the middle, then think carefully about whether a list is even the right structure.

Here’s the guidance I give my teams:

  • Use remove(int index) when list order matters and you remove by position (UI ordering, pagination, ranked items).
  • Use remove(Object o) when order matters but you identify elements by value.
  • Use Iterator.remove() when removing while iterating.
  • Use removeIf(Predicate) when filtering by condition and you don’t need the removed values.

That last one is worth a quick example.

import java.util.ArrayList;

import java.util.List;

public class RemoveIfExample {

public static void main(String[] args) {

List scores = new ArrayList();

scores.add(95);

scores.add(40);

scores.add(82);

scores.add(30);

scores.removeIf(score -> score < 50);

System.out.println(scores); // [95, 82]

}

}

If you only need to remove based on a condition, removeIf is clearer and less error‑prone than manual index removal.

Performance characteristics you should factor in

I don’t obsess over micro‑benchmarks, but I always align my data structures with expected usage. The performance of remove(int index) depends on the list type.

ArrayList

  • Access by index: O(1)
  • Remove by index: O(n) due to shifting elements
  • Practical impact: If you remove near the end, it’s cheap; near the start, it can be noticeably slower on large lists

LinkedList

  • Access by index: O(n)
  • Remove by index: O(n) because it must traverse to the index, then relink
  • Practical impact: It’s not a win for random removal; it shines only for frequent additions/removals at the ends

CopyOnWriteArrayList

  • Remove by index: O(n) and creates a new array
  • Practical impact: Great for read‑heavy, write‑rare scenarios; expensive for frequent removals

If I have a list with tens of thousands of items and I remove in the middle inside a tight loop, I expect noticeable overhead. The cost is usually still in the sub‑millisecond range for small lists, but you can easily push it into 10–15ms per removal on larger lists in hot paths. That’s enough to hurt UI responsiveness or request latency.

When you feel performance pressure, consider:

  • Collect indexes to remove and process them in reverse order
  • Use a different data structure if removal by index is frequent
  • Replace list removal with lazy filtering and rebuild once

Traditional vs modern removal strategies (table)

When I review code, I often see manual index removal where more expressive APIs would be clearer. This table helps you pick a modern approach without sacrificing correctness.

Scenario

Traditional approach

Modern approach I recommend —

— Remove all invalid items

Loop with index and remove(i)

removeIf(predicate) Remove during iteration

Index loop with manual adjustment

Iterator.remove() Remove by index from UI

list.remove(i) with no validation

removeAt(list, i) with bounds check Remove by value in List

list.remove(2) (ambiguous)

list.remove(Integer.valueOf(2))

You’ll notice I use modern APIs mainly for clarity and safety. Java has grown more expressive, and in 2026 most teams are comfortable with these idioms.

Edge cases that bite in production

These are the top edge cases I watch for when working with remove(int index).

1) Index from external input

If the index comes from a UI or API, validate it. A stale index is common when the list changes between user interactions.

2) Concurrent modification

If another thread modifies the list while you remove by index, you can get ConcurrentModificationException or worse, inconsistent behavior. For shared mutable lists, use synchronization or thread‑safe collections.

3) Off‑by‑one errors

A common bug in pagination code: the UI might be 1‑based (first item is “1”), but the list is 0‑based. Make the conversion explicit and document it.

4) Autoboxing confusion

List with remove(2) is the classic case. You meant to remove value 2, but you removed the third element. I treat this as a code smell and fix it in code review.

5) Removing from unmodifiable lists

List.of(...) and Collections.unmodifiableList(...) will throw UnsupportedOperationException on removal. I recommend copying to a mutable list before calling remove.

List immutable = List.of("alpha", "beta", "gamma");

List mutable = new ArrayList(immutable);

mutable.remove(1);

A more realistic example: removing by index in a CRUD flow

Let’s say you’re managing a list of shipping addresses in a user profile. The UI sends the index of the address to delete. You need to validate the index, remove it, and return the updated list.

import java.util.ArrayList;

import java.util.List;

public class AddressService {

public static List removeAddress(List addresses, int index) {

if (index = addresses.size()) {

throw new IllegalArgumentException(

"Address index " + index + " is out of range");

}

// Remove and log the deleted address for auditing

String removed = addresses.remove(index);

System.out.println("Removed address: " + removed);

return addresses;

}

public static void main(String[] args) {

List addresses = new ArrayList();

addresses.add("101 Bay Street, Toronto");

addresses.add("77 King Road, Seattle");

addresses.add("12 Market Lane, Austin");

removeAddress(addresses, 1);

System.out.println(addresses);

}

}

Output:

Removed address: 77 King Road, Seattle
[101 Bay Street, Toronto, 12 Market Lane, Austin]

This mirrors what I see in real services: removal is tied to auditing, logging, and validation. remove(int index) is the key operation, but it rarely lives alone.

How remove(int index) interacts with List implementations

To help you choose the right list, I use a quick checklist:

  • ArrayList: Best default choice; fast random access; removal in the middle costs shifting
  • LinkedList: Poor random access; removal by index still O(n); useful for queue‑like operations
  • Vector: Legacy synchronized list; similar to ArrayList but heavier; I rarely use it in modern code
  • CopyOnWriteArrayList: Safe for concurrent reads; expensive writes; good for read‑mostly configuration lists

If your application is heavy on random removals by index, consider whether you can redesign the data flow so you filter into a new list instead of mutating in place. In 2026, with more emphasis on immutability and functional patterns, that approach often improves reasoning and reduces bugs.

H2: Visualizing the shift with a tiny trace

Whenever I teach this method, I walk through a short trace. It’s worth doing at least once to cement the mental model.

Initial list:

  • Index 0: A
  • Index 1: B
  • Index 2: C
  • Index 3: D
  • Index 4: E

Call remove(2):

  • Removed element: C
  • New list order: A, B, D, E
  • New indexes: A=0, B=1, D=2, E=3

Notice that D and E are still in order, but they have new indices. If any other part of your code refers to the old index, it now points to a different element. This is why index values should be treated as short‑lived or recalculated from the current list.

H2: Removing from the end vs removing from the start

One practical rule I use: removal near the end is cheap in an ArrayList, removal near the start is expensive. It’s not just theoretical; it shows up in UI list manipulation and server‑side batching.

  • Removing the last element is basically a constant‑time operation in ArrayList because no shifting occurs.
  • Removing the first element forces all remaining elements to shift left, which is O(n).

If you need to remove a lot of elements by index, process indices in descending order. This reduces shifting for elements you haven’t removed yet. It also keeps indices stable relative to the remaining items.

Here’s the safe removal pattern when you already know the indices you want to remove:

import java.util.ArrayList;

import java.util.Arrays;

import java.util.List;

public class BatchRemoveByIndex {

public static void main(String[] args) {

List items = new ArrayList(Arrays.asList(

"A", "B", "C", "D", "E", "F"));

int[] indicesToRemove = {1, 3, 4};

// Sort descending to avoid index shifts affecting later removals

for (int i = indicesToRemove.length - 1; i >= 0; i--) {

items.remove(indicesToRemove[i]);

}

System.out.println(items); // [A, C, F]

}

}

This pattern looks simple, but it avoids a surprisingly common bug when developers remove indices in ascending order.

H2: A stronger example with domain objects

I like to show remove(int index) with objects to highlight that the return value is useful for logging, auditing, or undo features.

import java.util.ArrayList;

import java.util.List;

class CartItem {

private final String sku;

private final int qty;

CartItem(String sku, int qty) {

this.sku = sku;

this.qty = qty;

}

public String getSku() { return sku; }

public int getQty() { return qty; }

@Override

public String toString() {

return sku + " x" + qty;

}

}

public class CartRemoveExample {

public static void main(String[] args) {

List cart = new ArrayList();

cart.add(new CartItem("SKU-RED-01", 2));

cart.add(new CartItem("SKU-BLU-02", 1));

cart.add(new CartItem("SKU-GRN-03", 4));

int removeIndex = 1; // user removed the second item

CartItem removed = cart.remove(removeIndex);

System.out.println("Removed item: " + removed);

System.out.println("Cart now: " + cart);

}

}

This mirrors how I implement cart removal in production: UI passes an index, backend validates it, removes the item, logs the removal, and returns the updated list.

H2: Overload traps and how I prevent them in reviews

The remove(int index) vs remove(Object o) ambiguity is the most damaging trap with List. I apply two simple guardrails:

1) Use Integer variables when removing by value:

Integer valueToRemove = 2;

list.remove(valueToRemove);

2) In code review, I flag any list.remove(numberLiteral) against a List as a red‑flag. I prefer either Integer.valueOf(literal) or a named variable.

The deeper issue is intent. Good code is precise about intent. remove(2) is easy to misread. remove(Integer.valueOf(2)) is unambiguous. The extra characters are worth it.

H2: Removing while iterating over indices from UI selections

UI frameworks often return a list of selected row indices. If you remove in ascending order, you can remove the wrong rows because indices shift. Here’s the safe pattern I use in desktop and web apps:

import java.util.ArrayList;

import java.util.Arrays;

import java.util.List;

public class RemoveSelectedRows {

public static void main(String[] args) {

List rows = new ArrayList(Arrays.asList(

"Row0", "Row1", "Row2", "Row3", "Row4"));

List selected = Arrays.asList(1, 3);

selected.sort((a, b) -> b - a); // descending

for (int index : selected) {

rows.remove(index);

}

System.out.println(rows); // [Row0, Row2, Row4]

}

}

I like to enforce descending removal whenever I see index lists. It turns a fragile UI interaction into a deterministic one.

H2: How remove(int index) behaves with subList

subList creates a view, not a copy. When you call remove(int index) on a subList, you are modifying the original list. That’s powerful and dangerous.

import java.util.ArrayList;

import java.util.List;

public class SubListRemoveExample {

public static void main(String[] args) {

List all = new ArrayList();

all.add("A");

all.add("B");

all.add("C");

all.add("D");

List middle = all.subList(1, 3); // [B, C]

middle.remove(0); // removes B from the sublist

System.out.println("Sublist: " + middle); // [C]

System.out.println("Original: " + all); // [A, C, D]

}

}

This is great when you want to operate on a slice, but it can surprise developers who assume subList is a copy. If you need isolation, wrap it in new ArrayList(subList) before removing.

H2: Error handling patterns that scale with APIs

In internal code, I let IndexOutOfBoundsException surface. In a service layer, I convert it to a domain exception or validation error. That way I can return a clean API response instead of a generic stack trace.

Here’s a pattern I’ve used in REST controllers:

public class AddressController {

public List deleteAddress(List addresses, int index) {

try {

return AddressService.removeAddress(addresses, index);

} catch (IllegalArgumentException ex) {

// translate to HTTP 400 or a structured error response

throw new BadRequestException(ex.getMessage());

}

}

}

The point isn’t the exception class; it’s the separation of concerns. Validation is closer to the edge, removal stays in the service, and the list logic remains clean.

H2: Using remove(int index) in undo/redo stacks

Because remove(int index) returns the removed element, it pairs well with undo/redo. The removed item can be stored along with its index so you can re‑insert it later.

import java.util.ArrayList;

import java.util.List;

class RemovalAction {

final int index;

final E element;

RemovalAction(int index, E element) {

this.index = index;

this.element = element;

}

}

public class UndoableRemove {

public static void main(String[] args) {

List items = new ArrayList();

items.add("Alpha");

items.add("Beta");

items.add("Gamma");

int index = 1;

String removed = items.remove(index);

RemovalAction action = new RemovalAction(index, removed);

// Undo

items.add(action.index, action.element);

System.out.println(items); // [Alpha, Beta, Gamma]

}

}

This is the cleanest pattern I know for undo in list editors. You remove by index, capture the removed element, and reinsert it at the same index when needed.

H2: Pitfalls with primitive arrays vs lists

Sometimes developers confuse array behavior with list behavior. Arrays don’t shift elements when you “remove” something, because arrays can’t shrink. Lists do. This is another reason remove(int index) is attractive: it handles shifting for you.

If you’re migrating from arrays to lists, watch out for assumptions about fixed size. With a list, size changes immediately and indices shift. That’s often a benefit, but it means you can’t safely keep old indices.

H2: Removing from a list used by multiple components

In larger applications, multiple components can reference the same list. If one component removes by index, another component might still hold a cached index. This leads to confusing bugs where a UI selection removes the wrong item.

My strategy is simple:

  • Avoid storing raw indices for long periods.
  • Store stable IDs (like UUIDs) and map to indices just before removal.
  • If you must store indices, use them immediately and invalidate them after a mutation.

That alone prevents a large class of UI‑driven bugs.

H2: remove(int index) in concurrent contexts

Most List implementations are not thread‑safe. If two threads call remove(int index) at the same time, or one thread modifies the list while another iterates, you can corrupt state or get ConcurrentModificationException.

You have a few options:

  • Use synchronization around modifications.
  • Use thread‑safe lists like CopyOnWriteArrayList for read‑heavy use cases.
  • Avoid sharing mutable lists across threads; pass copies or immutable snapshots.

I tend to favor immutability for shared data and reserve synchronized lists for tight, controlled sections where mutation is necessary.

H2: remove(int index) in tests — assert the shift

When writing tests for list manipulation, I assert both the removed element and the resulting list order. This guards against off‑by‑one mistakes and future refactors.

import java.util.ArrayList;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

public class RemoveIndexTest {

@org.junit.jupiter.api.Test

void removesAndShiftsCorrectly() {

List list = new ArrayList();

list.add("A");

list.add("B");

list.add("C");

String removed = list.remove(1);

assertEquals("B", removed);

assertEquals(List.of("A", "C"), list);

}

}

This is a small test, but it catches a surprising number of regressions when list manipulation gets more complex.

H2: Patterns for safe removal in services

In services, I prefer to funnel removal into a single method that validates the index, performs the removal, and logs it. That gives me consistent behavior and a natural place to add metrics.

import java.util.List;

import java.util.logging.Logger;

public class ListRemovalService {

private static final Logger LOG = Logger.getLogger("ListRemovalService");

public static E removeAt(List list, int index) {

if (index = list.size()) {

throw new IllegalArgumentException(

"Index " + index + " out of bounds for size " + list.size());

}

E removed = list.remove(index);

LOG.info("Removed element at index " + index + ": " + removed);

return removed;

}

}

This is just a refinement of the earlier helper, but it scales well when multiple components need consistent behavior.

H2: remove(int index) and serialization

If you serialize a list and later deserialize it, the indices are based on the current order. This seems obvious, but I’ve seen bugs where stored indices were used after a list changed. If you persist an index, treat it as a positional hint, not a permanent identifier.

I avoid storing indices in databases unless the list order is immutable or versioned. If order can change, I store a stable ID and compute the index on demand.

H2: Comparing removal strategies by intent

Sometimes it’s not about performance or correctness; it’s about expressing intent. Here’s how I decide:

  • “Remove the third item” → remove(int index)
  • “Remove this item” → remove(Object o) or removeIf by ID
  • “Remove all items matching a rule” → removeIf with a predicate
  • “Remove items while scanning” → Iterator.remove()

I’ve found that matching the method to intent reduces mental load and code review friction.

H2: Practical safeguards you can add today

Here are the safeguards I add in mature codebases:

1) Guard against stale indexes:

if (index >= list.size()) {

throw new IllegalArgumentException("Stale index: " + index);

}

2) Normalize user input:

int zeroBased = userIndex - 1; // if UI is 1-based

3) Avoid magic numbers:

int removeIndex = selectedRowIndex;

list.remove(removeIndex);

These small choices reduce confusion and make code easier to maintain.

H2: When you should not use remove(int index)

There are times where removal by index is a poor fit:

  • When order doesn’t matter: use a Set or Map and remove by key/value.
  • When removal is frequent and list is huge: consider a different structure or rebuild a filtered list instead of repeated shifting.
  • When you need stable identifiers: use IDs rather than indices; indices are inherently volatile.
  • When you’re in a concurrent context: prefer immutable snapshots or thread‑safe collections.

I treat this as a design decision. The method is fine; it’s the context that determines whether it’s the right tool.

H2: Removing with streams and why I avoid it here

Developers sometimes try to remove items by index using streams. I avoid that for this case because streams are optimized for transformations, not in‑place mutation. If you want a new list without a specific index, you can do:

List filtered = IntStream.range(0, list.size())

.filter(i -> i != indexToRemove)

.mapToObj(list::get)

.toList();

This creates a new list without the element at the index. It’s a viable approach when you want immutability, but it’s not a drop‑in replacement for remove(int index) because it doesn’t return the removed element and it creates a new list. I choose it only when I want a non‑mutating workflow.

H2: Performance notes from the field

I’ve seen performance issues crop up in these scenarios:

  • Removing from the front of large ArrayLists in a loop
  • Removing large numbers of items one by one instead of filtering once
  • Using LinkedList for random removals under the assumption it’s faster

My rule of thumb: if you remove many items, prefer a single pass that builds a new list. If you remove a few, remove(int index) is fine. If you remove by index in a loop and performance matters, reverse the order or rethink the structure.

H2: A pattern for bulk removal while preserving order

Sometimes you have a list of indices to remove and you want to preserve the relative order of the remaining elements. Removing in reverse order does that, but if you have a large list and many removals, a filter‑based approach can be more efficient and easier to reason about.

Here’s a clean approach using a boolean “keep” mask:

import java.util.ArrayList;

import java.util.Arrays;

import java.util.List;

public class BulkRemoveWithMask {

public static void main(String[] args) {

List items = new ArrayList(Arrays.asList(

"A", "B", "C", "D", "E", "F"));

boolean[] remove = new boolean[items.size()];

remove[1] = true; // remove B

remove[4] = true; // remove E

List kept = new ArrayList();

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

if (!remove[i]) {

kept.add(items.get(i));

}

}

System.out.println(kept); // [A, C, D, F]

}

}

This approach avoids repeated shifting and is easy to test. I use it when the removal set is large or known in advance.

H2: Clarifying 0-based vs 1-based indexes in APIs

If your API accepts an index from clients, the question is whether it’s 0‑based or 1‑based. I always document this explicitly and convert at the boundary.

If I accept 1‑based indexes from a UI, I convert before removal:

int zeroBased = requestIndex - 1;

list.remove(zeroBased);

I also validate that requestIndex is at least 1. That single check prevents negative indexes after conversion.

H2: The most common bugs, summarized

Here’s a compact list of the bugs I see most often and how I fix them:

  • Removed wrong element: caused by List overload confusion. Fix with explicit boxing.
  • Skipped elements in a loop: caused by forward index removal. Fix by iterating backward or using an iterator.
  • Index out of bounds in production: caused by stale or unvalidated input. Fix with bounds checks and clear errors.
  • ConcurrentModificationException: caused by modification while iterating or shared mutation. Fix with synchronization or immutable snapshots.
  • UnsupportedOperationException: caused by removing from unmodifiable lists. Fix by copying to a mutable list.

If you handle these five, you’ll avoid the majority of removal‑related incidents.

H2: A checklist I run before using remove(int index)

Before I use remove(int index) in production code, I ask myself:

1) Is the index valid and derived from the current list state?

2) Is the list mutable?

3) Do I need the removed element for logging or undo?

4) Am I in a loop where index shifts could cause skips?

5) Is the list shared across threads or components?

If I can answer these, I’m confident the removal is safe and correct.

H2: Final takeaway

remove(int index) is simple, but it’s not trivial. It removes the element at a given index, shifts everything to the left, and returns the removed element. That behavior is consistent across list implementations, but the performance and safety implications vary widely depending on context.

When you use it with a clear mental model, validated indices, and the right iteration pattern, it’s a clean and powerful tool. When you use it casually, it’s a source of subtle bugs. I’ve been burned by it once, and I’ve treated it with respect ever since.

If you keep the shift behavior in mind, avoid the overload trap, and choose the right list for the job, remove(int index) will do exactly what you expect — no surprises, no lost records, and no midnight bug hunts.

Scroll to Top