When an interviewer asks "what is a List in Java?", they are not testing whether you can recite a definition. They want to know if you understand the contract an interface defines and whether you can pick the right implementation for a workload. This page gives you both: the List contract, and how ArrayList, LinkedList and Vector fulfil it in very different ways.
If you have not yet seen the big picture of Iterable → Collection → List/Set/Queue, skim
the Java Collections Framework overview
first — this page zooms into one branch of that hierarchy.
What the List interface guarantees
java.util.List extends Collection and adds three guarantees that a plain Collection
does not make:
- Insertion order is preserved. Elements stay in the order you added them.
- Duplicates are allowed.
list.add("Java")twice gives you a list of size 2. - Index-based access. You can call
get(2),set(2, x),add(2, x)andremove(2)using positions, exactly like an array.
That third point is the real differentiator. A Set also stores elements, but it has no concept of "the element at position 4". List does.
Because List is an interface, you always instantiate a concrete class:
List<String> skills = new ArrayList<>(); // the default choice
List<String> queue = new LinkedList<>(); // when you mostly add/remove at ends
List<String> legacy = new Vector<>(); // legacy, avoid in new code
Declaring the variable as List<String> instead of ArrayList<String> is called
programming to the interface. It lets you change the implementation in one place
without touching any method that consumes the list.
Core methods you will use every day
These come up constantly in real code and in machine-coding rounds:
import java.util.ArrayList;
import java.util.List;
public class ListBasics {
public static void main(String[] args) {
List<String> stack = new ArrayList<>();
stack.add("Java"); // append at end
stack.add("Spring Boot");
stack.add(1, "SQL"); // insert at index 1, shifts the rest right
System.out.println(stack.get(0)); // Java
System.out.println(stack.indexOf("SQL")); // 1
System.out.println(stack.contains("React")); // false
stack.set(2, "Spring"); // replace, does NOT shift anything
stack.remove("SQL"); // remove by value (first match)
stack.remove(0); // remove by index
System.out.println(stack); // [Spring]
System.out.println(stack.size()); // 1
}
}
Notice the two overloads of remove: remove(int index) and remove(Object o). With a
List<Integer>, list.remove(1) removes the element at index 1, not the value 1 —
to remove the value you must write list.remove(Integer.valueOf(1)).
Common mistake: A common mistake beginners make is calling
list.remove(i)on aList<Integer>expecting value-based removal. Java picks theremove(int)overload, deletes by index, and the bug silently corrupts your data. Useremove(Integer.valueOf(i))when you mean the value.
ArrayList: the default implementation
ArrayList stores elements in a plain Object[] array that grows by roughly 50% when it
fills up. That single design decision explains its entire performance profile:
get(i)andset(i, x)are O(1) — direct array indexing.add(x)at the end is amortized O(1) — occasionally a resize copies the array.add(i, x)andremove(i)in the middle are O(n) — elements shift.- Memory is compact and cache-friendly, because elements sit next to each other.
For the full story of the growth mechanics, capacity management and iteration rules, read ArrayList internal working and operations.
LinkedList: the doubly-linked alternative
LinkedList stores each element in its own node with prev and next references. It
implements both List and Deque, so it can act as a stack or a queue:
addFirst(x),addLast(x),removeFirst(),removeLast()are all O(1).get(i)is O(n) — the list walks node by node from the nearest end.- Removal via an iterator that is already positioned is O(1).
- Each element costs extra memory for the node object and two references.
In practice, LinkedList wins in far fewer cases than most tutorials suggest. The head-to-head numbers are in ArrayList vs LinkedList.
Vector: the legacy synchronized List
Vector predates the Collections Framework (it shipped with Java 1.0) and was retrofitted
to implement List. It behaves like an ArrayList with two differences:
- Every method is synchronized, so single operations are thread-safe but slower.
- It doubles capacity on resize instead of growing by 50%.
The synchronization sounds attractive until you realize it does not protect compound operations. This classic check-then-act sequence is still broken with Vector:
// Broken even though Vector methods are synchronized:
if (!vector.contains(x)) { // thread B can add x right here
vector.add(x); // now you have a duplicate
}
Two threads can interleave between contains and add. You would need external locking
anyway — at which point Vector's per-method locks are pure overhead.
Interview note: If asked "is Vector thread-safe?", the strong answer is: "Each method is synchronized, but compound operations like check-then-add still need external synchronization, so Vector gives a false sense of safety. Modern code uses
Collections.synchronizedListorCopyOnWriteArrayListinstead."
Performance comparison at a glance
| Operation | ArrayList | LinkedList | Vector |
|---|---|---|---|
get(i) / set(i, x) |
O(1) | O(n) | O(1) + lock |
add(x) at end |
Amortized O(1) | O(1) | Amortized O(1) + lock |
add(0, x) at head |
O(n) | O(1) | O(n) + lock |
add(i, x) in middle |
O(n) | O(n) to find + O(1) to link | O(n) + lock |
remove(i) in middle |
O(n) | O(n) to find + O(1) to unlink | O(n) + lock |
contains(x) |
O(n) | O(n) | O(n) + lock |
| Memory per element | Low | High (node + 2 refs) | Low |
| Thread safety | No | No | Per-method only |
Note the LinkedList middle-insert row: linking the node is O(1), but reaching index i
is O(n). Total cost is still linear, which surprises many candidates.
Iterating and sorting a List
Iteration and sorting come up in nearly every practical task. Here is a runnable example covering the three iteration styles plus sorting:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public class ListIteration {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>(List.of(72, 95, 61, 88));
// 1. for-each: cleanest for read-only passes
for (int s : scores) {
System.out.print(s + " ");
}
System.out.println();
// 2. Iterator: the ONLY safe way to remove while iterating
Iterator<Integer> it = scores.iterator();
while (it.hasNext()) {
if (it.next() < 70) {
it.remove(); // safe removal
}
}
System.out.println(scores); // [72, 95, 88]
// 3. Index-based: when you need the position
for (int i = 0; i < scores.size(); i++) {
System.out.println(i + " -> " + scores.get(i));
}
scores.sort(Comparator.reverseOrder());
System.out.println(scores); // [95, 88, 72]
}
}
Run this and try replacing it.remove() with scores.remove(...) inside the loop — you
will get a ConcurrentModificationException. The list's fail-fast iterator detects that
the list changed behind its back.
Pro tip: For simple "remove everything matching a condition" jobs, skip the iterator entirely:
scores.removeIf(s -> s < 70)does the same thing in one line and is the idiomatic modern form.
Immutable lists: List.of and List.copyOf
Since Java 9, List.of(...) creates compact immutable lists. They throw
UnsupportedOperationException on any mutation and reject null elements. Use them for
constants and safe return values:
List<String> tracks = List.of("Java", "Testing", "DevOps");
// tracks.add("AI"); // throws UnsupportedOperationException
Arrays.asList(...) is the older cousin with a trap: it is fixed-size but not
immutable — set works, add throws. When you need a mutable list from literals, write
new ArrayList<>(List.of(...)).
Which one should you pick?
- Default: ArrayList. Fast reads, compact memory, amortized O(1) appends.
- Heavy add/remove at both ends, or you need a Deque: LinkedList — though
ArrayDequeusually beats it for pure queue/stack work. - Vector: only when maintaining legacy code that already uses it.
- Concurrent reads with rare writes:
CopyOnWriteArrayList.
This decision is one of the most common questions in Java collections interview rounds, usually followed by "why?" — and the table above is your answer. If you are building these fundamentals as part of a bigger goal, the structured path in our Java Full Stack course covers collections with the same interview-first approach.
Frequently Asked Questions
What is the difference between List and ArrayList in Java?
Does a List in Java allow duplicate elements?
Is Vector still used in modern Java code?
Which List implementation should I use by default?
Can I add or remove elements from a List created with List.of()?
How do I make a List thread-safe?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

