List in Java: ArrayList, LinkedList, Vector and Stack
A List keeps insertion order and allows duplicates. ArrayList is the default; LinkedList wins only at the ends.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
What a List is
A List is an ordered collection. Elements keep their position, duplicates are allowed, and every element is reachable by an index starting at zero.
List<String> topics = new ArrayList<>();
topics.add("variables");
topics.add("loops");
topics.add(1, "operators"); // insert at a position
topics.add("loops"); // duplicates are fine
System.out.println(topics); // [variables, operators, loops, loops]
System.out.println(topics.get(2));
System.out.println(topics.indexOf("loops")); // 2, the first match
System.out.println(topics.lastIndexOf("loops")); // 3
topics.set(0, "data types");
topics.remove("loops"); // removes the first match
topics.remove(0); // removes by indexremove(int)andremove(Object)are different overloads. On aList<Integer>,list.remove(2)removes the element at index 2, whilelist.remove(Integer.valueOf(2))removes the value 2. This is a classic interview trap and a real bug.
ArrayList
Backed by an array that is replaced with a larger one when it fills. Access by index is a direct array read.
List<Integer> scores = new ArrayList<>(100); // pre-sized, avoids regrowth| Operation | Cost |
|---|---|
get(i), set(i, e) | O(1) |
add(e) at the end | O(1) amortised |
add(i, e), remove(i) | O(n), elements shift |
contains, indexOf | O(n) |
LinkedList
A doubly linked list. Each element is a node holding the value and links to its neighbours.
| Operation | Cost |
|---|---|
addFirst, addLast, removeFirst, removeLast | O(1) |
get(i) | O(n), it walks the chain |
add(i, e) once positioned | O(1), but reaching the position is O(n) |
// Very slow: each get walks from an end
LinkedList<String> list = new LinkedList<>(data);
for (int i = 0; i < list.size(); i++) {
process(list.get(i)); // O(n) each time, O(n squared) overall
}
// Correct for a linked list
for (String value : list) {
process(value); // one pass
}ArrayList compared with LinkedList
| Aspect | ArrayList | LinkedList |
|---|---|---|
| Index access | O(1) | O(n) |
| Insert or remove at the end | O(1) amortised | O(1) |
| Insert or remove at the front | O(n) | O(1) |
| Memory per element | Low | Higher, two links per node |
| Cache behaviour | Good, contiguous | Poor, scattered nodes |
| Also implements | RandomAccess | Deque |
In practice ArrayList wins almost every time, because contiguous memory is far friendlier to the processor cache than pointer chasing. When you genuinely need queue behaviour at both ends, ArrayDeque beats LinkedList as well.
Vector and Stack
Vector<String> old = new Vector<>(); // synchronised, legacy
Stack<String> stack = new Stack<>(); // extends Vector, legacy
Deque<String> modern = new ArrayDeque<>(); // use this for a stack
modern.push("first");
modern.push("second");
System.out.println(modern.pop()); // secondVector synchronises every method, which costs time and still does not make a sequence of calls atomic. Stack extends it and therefore exposes add(index, element), letting a caller insert into the middle of a stack. Both are kept only for compatibility.
Useful operations
List<String> names = new ArrayList<>(List.of("Ravi", "Anita", "Meera"));
Collections.sort(names); // natural order
names.sort(Comparator.comparing(String::length));
Collections.reverse(names);
Collections.shuffle(names);
List<String> view = names.subList(0, 2); // a live view, not a copy
List<String> readOnly = Collections.unmodifiableList(names);
names.replaceAll(String::toUpperCase);
names.removeIf(name -> name.startsWith("A"));subList returns a view. Changes through it affect the original, and structurally modifying the original invalidates it.
Removing safely while iterating
// Throws ConcurrentModificationException
for (String name : names) {
if (name.isBlank()) {
names.remove(name);
}
}
// Correct options
names.removeIf(String::isBlank);
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().isBlank()) {
it.remove();
}
}Common mistakes
- Confusing
remove(int)withremove(Object)on a list of integers. - Using
LinkedListwith an indexed loop. - Calling
addon the fixed size list fromArrays.asList. - Modifying a list during an enhanced
forloop. - Choosing
Vectorfor thread safety instead of a proper concurrent collection.
Best practices
- Default to
ArrayList. - Declare the variable as
List. - Pre-size when the count is known and large.
- Use
removeIfrather than manual iteration. - Return
List.copyOfor an unmodifiable view from getters.
Practice
- Predict the result of
list.remove(1)on aList<Integer>containing 10, 20, 30, then remove the value 20. - Measure an indexed loop over 100000 elements in both implementations and explain the difference.
- Remove every empty string from a list in two different correct ways.
- Why does
Stackallow inserting into the middle, and why is that a problem? - Sort a list of names by length and then alphabetically for equal lengths.
Conclusion
Use ArrayList unless you have a measured reason not to, keep the variable typed as List, remember the remove overload trap, and never structurally modify a list while a plain loop is walking it.