List

The List interface — ordered, indexed, allows duplicates. When to use ArrayList vs LinkedList, and the patterns senior developers rely on.

Last reviewed: January 2025

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: A List is an ordered collection that allows duplicates and provides index-based access. ArrayList (backed by an array) is the default choice for 95% of use cases. LinkedList is only better when you frequently insert/delete at the beginning or middle. Use List.of() for immutable lists.

🔍 What Is It?

List Interface

java.util.List extends Collection and adds:

  • →get(int index) — access by position
  • →set(int index, E element) — replace at position
  • →add(int index, E element) — insert at position
  • →remove(int index) — remove at position
  • →indexOf(Object o) — find first occurrence
  • →subList(int from, int to) — view of a portion
  • →listIterator() — bidirectional iteration

Key Implementations:

  • →ArrayList — resizable array; O(1) random access; O(n) insert/delete in middle
  • →LinkedList — doubly-linked list; O(n) random access; O(1) insert/delete at known position; also implements Deque
  • →Vector — legacy synchronized ArrayList; avoid in new code
  • →CopyOnWriteArrayList — thread-safe; creates copy on every write; best for read-heavy concurrent access
  • →List.of() — immutable list (Java 9+); throws UnsupportedOperationException on modification
  • →Arrays.asList() — fixed-size list backed by array; allows set() but not add/remove

🎯 Why Do We Need It?

Lists solve ordered collection needs:

  • →Ordered data — results from a database query, items in a shopping cart, steps in a workflow
  • →Index access — pagination (get items 20-40), random sampling
  • →Duplicates allowed — unlike Set, List allows the same element multiple times
  • →Iteration order guaranteed — elements always come out in insertion order

In enterprise Java, List is the most commonly used collection type — most service methods return List<SomeEntity>.

🏗️ What Happens in Real Projects?

Common patterns in enterprise code:

// Service returns List
public List<Order> getOrdersByCustomer(String customerId) {
    return orderRepository.findByCustomerId(customerId);
}

// Immutable list for constants
private static final List<String> VALID_CURRENCIES = List.of("USD", "EUR", "GBP");

// Defensive copy to prevent external modification
public List<LineItem> getLineItems() {
    return Collections.unmodifiableList(lineItems);
}

// Sorting
orders.sort(Comparator.comparing(Order::getCreatedAt).reversed());

⚙️ How It Works

ArrayList Internals:

  • →Backed by an Object[] array
  • →Initial capacity: 10
  • →When full, creates new array 1.5x the size and copies elements
  • →This resize is O(n) but happens rarely — amortized O(1) add
  • →Random access: O(1) — direct array index
  • →Insert/delete in middle: O(n) — must shift elements

LinkedList Internals:

  • →Each element is a Node with prev and next pointers
  • →No array — no resizing
  • →Random access: O(n) — must traverse from head
  • →Insert/delete at known position: O(1) — just update pointers
  • →Higher memory overhead: each node has two extra references

💻 Example

List Patterns in Enterprise Code — The List operations you use in every Java project.

java
// Creating lists
List<String> mutable = new ArrayList<>();          // empty, can add/remove
List<String> fromArray = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> immutable = List.of("a", "b", "c");  // Java 9+, cannot modify
List<String> copy = new ArrayList<>(immutable);    // mutable copy of immutable

// Basic operations
mutable.add("hello");
mutable.add(0, "first");          // insert at index 0
mutable.set(1, "updated");        // replace at index 1
mutable.remove(0);                // remove by index
mutable.remove("hello");          // remove by value (first occurrence)
String item = mutable.get(0);     // O(1) for ArrayList
boolean has = mutable.contains("hello");
int idx = mutable.indexOf("hello");

// Bulk operations
mutable.addAll(List.of("x", "y", "z"));
mutable.removeAll(List.of("x", "y"));
mutable.retainAll(List.of("z", "hello"));  // keep only these

// Iteration
for (String s : mutable) { System.out.println(s); }
mutable.forEach(System.out::println);
mutable.stream().filter(s -> s.length() > 3).forEach(System.out::println);

// Sorting
List<Integer> numbers = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9));
Collections.sort(numbers);                          // natural order
numbers.sort(Comparator.reverseOrder());            // reverse
numbers.sort(Comparator.comparingInt(Math::abs));   // by absolute value

// Sublist (view — changes affect original!)
List<Integer> sub = numbers.subList(1, 4);  // elements 1, 2, 3 (not 4)

🤔 Common Confusions

❓ When should I use LinkedList instead of ArrayList?

Almost never in practice. LinkedList is theoretically faster for insert/delete at the beginning, but the cache locality of ArrayList makes it faster in practice for almost all real workloads. LinkedList also uses more memory (two pointers per node). Use LinkedList only if you are implementing a queue/deque and need O(1) add/remove from both ends — and even then, ArrayDeque is usually better.

❓ What is the difference between List.of() and Arrays.asList()?

List.of() (Java 9+) returns a truly immutable list — you cannot add, remove, or set elements, and it does not allow null values. Arrays.asList() returns a fixed-size list backed by the array — you can set() elements but cannot add() or remove(). Also, Arrays.asList() allows null. Use List.of() for constants and return values you want to protect.

❓ Does Collections.sort() modify the original list?

Yes. Collections.sort() and list.sort() sort the list in-place. If you need a sorted copy without modifying the original, use: List<T> sorted = list.stream().sorted().collect(Collectors.toList()); or new ArrayList<>(list) then sort the copy.

🚨 Common Production Issues

ConcurrentModificationException — modifying a list while iterating it with for-each throws this. Use Iterator.remove(), removeIf(), or collect to a new list.

Returning mutable internal lists — callers can add/remove from your internal state. Return Collections.unmodifiableList() or a copy.

ArrayList with known large size — if you know the list will hold 10,000 items, initialize with new ArrayList<>(10000) to avoid 10+ resize operations.

Using List.subList() and then modifying the parent — subList is a view; modifying the parent list while holding a subList reference causes ConcurrentModificationException.

✅ Best Practices

Use ArrayList as the default List implementation — it is the right choice for 95% of use cases.

Declare variables as List<T>, not ArrayList<T> — program to the interface.

Use List.of() for immutable lists (constants, return values you want to protect).

Use removeIf() instead of iterating and removing — cleaner and avoids ConcurrentModificationException.

Initialize ArrayList with expected capacity when size is known to avoid resizing.

Use stream().toList() (Java 16+) instead of Collectors.toList() for unmodifiable results.

🏛️ Architect's Note

Senior Architect's Perspective

In high-performance systems, avoid large in-memory lists of JPA entities. Instead of loading 10,000 Order objects into a List<Order>, use pagination (Pageable in Spring Data), streaming (Stream<Order> from JPA), or database aggregation. Loading large result sets into memory is a common cause of OOM errors in production.

💬 Frequently Asked Questions

Is ArrayList thread-safe?

No. ArrayList is not synchronized. If multiple threads read and write concurrently, you will get ConcurrentModificationException or data corruption. Options: (1) Collections.synchronizedList(new ArrayList<>()) — synchronized wrapper, but iteration still needs external sync; (2) CopyOnWriteArrayList — thread-safe, creates a copy on every write, best for read-heavy workloads; (3) Use a concurrent queue (ConcurrentLinkedQueue) if you need concurrent add/remove.

What is the difference between remove(int index) and remove(Object o)?

For List<Integer>, list.remove(1) calls remove(int index) — removes element at index 1. To remove by value, you must box: list.remove(Integer.valueOf(1)). This is a common bug. In Java 21, you can use list.removeFirst() and list.removeLast() for clarity.

🎯 Key Takeaways

1ArrayList is the default List — use it for 95% of cases
2ArrayList: O(1) random access, O(n) insert/delete in middle
3LinkedList: O(n) random access, O(1) insert/delete at known position (rarely better in practice)
4List.of() creates immutable lists; Arrays.asList() creates fixed-size mutable lists
5Declare as List<T>, not ArrayList<T> — program to the interface
6Never modify a list while iterating with for-each — use removeIf() or Iterator

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Core Java learning path

Browse the full Core Java topic collection and learning order on SplashRide.

Core Java Interview Prep

Practice Core Java interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Set

The Set interface — no duplicates, fast membership testing. HashSet, LinkedHashSet, TreeSet — when to use each.

Java Map Interface Explained: HashMap, LinkedHashMap, and TreeMap

Learn the Java Map interface with HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap, key-value lookup, ordering, performance, and real-world examples.