A List is an ordered collection that allows duplicates and provides access by position.
We want to store ordered data and access it by its position.
But when writing the code, the IDE offers us two main options:
List<String> lista = new ArrayList<>();List<String> lista = new LinkedList<>();
Both work the same from the outside (they have the same add, get, remove methods). But under the hood, they are completely different worlds. Choosing the wrong one can make your application fly or crawl.
Today we’ll see what happens under the hood.
ArrayList
ArrayList is the default implementation and the one you’ll use 90% of the time.
How does it work internally?
Internally, an ArrayList IS a regular array (Object[]).
When its internal array gets full, ArrayList:
- Creates a new, larger array (usually 50% larger).
- Copies all elements from the old array to the new one.
- Discards the old array.
- Ultra-Fast Access (O(1)): Because it’s an array, if you request
get(5000), Java immediately knows how to calculate the exact memory address. No counting needed. - Cache Efficiency: Since the data is contiguous in memory, the processor loves iterating over it.
- Inserting/Deleting in the middle is slow (O(n)): If you have an array of 1,000 elements and insert something at position 0, Java has to shift all 1,000 elements one step to the right to make room.
LinkedList
LinkedList works with Nodes. Each element is an object that holds the data and two “pointers”: one to the next element and one to the previous (Doubly Linked List).
The data is NOT contiguous in memory. It is scattered across the heap, connected only by references.
- Fast Insertion/Deletion (O(1)): If you want to insert someone in the middle of a line of people holding hands, you only have to let go of two hands and grab the new person. Nobody else needs to move.
- Slow Access (O(n)): If you request
get(5000), Java has to start from the first node and jump from one to the next 5,000 times (“you know the next one, and you know the next one…”). There are no shortcuts. - Uses more memory: Each piece of data requires creating an extra object (Node) and storing two references (Next/Prev).
Complexity comparison (Big O)
| Operation | ArrayList | LinkedList | Winner |
|---|---|---|---|
get(index) | O(1) (Instant) | O(n) (Slow) | 🏆 ArrayList |
add(end) | O(1) (Very fast*) | O(1) (Fast) | Tie |
add(beginning) | O(n) (Shifts everything) | O(1) (Just pointers) | 🏆 LinkedList |
remove(index) | O(n) (Shifts elements) | O(n) (First locates the node) | Depends on access pattern |
| Memory | Low (Compact) | High (Extra Nodes) | 🏆 ArrayList |
*ArrayList’s add is fast amortized, except when it needs to resize the array.
LinkedList’s remove is O(1) only if the iterator is already positioned at the node. If it receives an index, it must first traverse the list.
Which one do I use?
As a starting criterion:
Use ArrayList as your default option.
Example code
The usage is identical thanks to Polymorphism.
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
public class BatallaListas {
public static void main(String[] args) {
// RECOMMENDED: Use the 'List' interface on the left
List<String> nombres = new ArrayList<>();
nombres.add("Ana");
nombres.add("Carlos");
nombres.add(1, "Beatriz"); // Insert in the middle (ArrayList struggles here)
System.out.println(nombres.get(1)); // Direct access (ArrayList excels here)
// If we wanted to switch to LinkedList, we only change the 'new':
// List<String> nombres = new LinkedList<>();
// The rest of the code does NOT change.
}
}
Conversion between arrays and lists
Sometimes you have an old Array and want to convert it to a modern List (or vice versa).
- Array -> List:
Arrays.asList()creates a fixed-size list. If you want to modify it, wrap it in an ArrayList.
String[] arr = {"A", "B", "C"};
List<String> lista = new ArrayList<>(Arrays.asList(arr));
- List -> Array:
String[] resultado = lista.toArray(new String[0]);