Understanding how ArrayList
and LinkedList
work under the hood is essential for writing efficient Java programs. Although both implement the List
interface and provide similar APIs, their internal data structures are fundamentally different, which impacts how they perform under various operations.
An ArrayList
is essentially a resizable array. Internally, it uses an array of Object
elements to store its data. When you add elements to an ArrayList
, they are placed into consecutive slots in the internal array.
get(i)
) is very fast — constant time O(1).Think of an ArrayList
like a tray of eggs — each egg slot (index) is fixed and next to the other. If you need to insert a new egg between two existing ones, you have to move all eggs after it to make space.
A LinkedList
is made up of nodes — each node contains a data element and two references:
This structure is known as a doubly linked list.
get(i)
) is slow — you must traverse from the head or tail — O(n).Imagine a train with carriages (nodes) linked together. To reach the 10th carriage, you must walk from the front or back. But adding or removing a carriage at either end is easy — just detach or attach it.
ArrayList
has lower memory overhead per element because it only stores the data.LinkedList
has higher memory usage because each node stores two extra references (prev and next).LinkedList
doesn’t need to allocate large contiguous memory blocks like ArrayList
.When an ArrayList
exceeds its capacity, it must:
A LinkedList
never resizes. It simply links a new node into the chain.
ArrayList:
[Index] 0 1 2 3 4
[A] [B] [C] [D] [E]
↑
Contiguous array in memory
LinkedList:
null ← [A] ↔ [B] ↔ [C] ↔ [D] ↔ [E] → null
Each node holds value + links
ArrayList
when you need fast random access and frequent additions at the end.LinkedList
when your program involves frequent insertions or deletions at the beginning or middle.Understanding these internal structures helps you make better choices for performance-sensitive applications.
Choosing between ArrayList
and LinkedList
depends on how your application interacts with data. Both implement the List
interface but differ significantly in performance, memory usage, and ideal use cases due to their internal structures.
Let’s explore when each is most appropriate, supported by practical scenarios and performance considerations.
ArrayList
When:If your program frequently accesses elements by index, ArrayList
is ideal. It supports constant-time access (O(1)
) because it’s backed by a dynamic array.
Example:
List<String> list = new ArrayList<>();
String item = list.get(5000); // Fast access
Use case: Displaying data in UI tables, managing cached items by index.
Appending elements is fast and efficient with ArrayList
, especially if you don’t reach the current capacity often. Resizing does occur, but it’s amortized and generally efficient.
Example:
list.add("New Item"); // Typically O(1)
Use case: Logging, collecting API responses, building a list of search results.
Each element in an ArrayList
only takes space for the object itself (plus array overhead), whereas LinkedList
stores additional references for previous and next nodes.
LinkedList
When:If your use case involves many insertions or deletions at the start or middle of the list, LinkedList
avoids expensive array shifting operations, making these operations more efficient — O(1) at the ends, O(n) in the middle but faster than ArrayList
when removing many elements.
Example:
list.add(0, "Urgent Task"); // O(1) for LinkedList
Use case: Task scheduling queues, history stacks, undo functionality.
With LinkedList
, iterators can be used to efficiently add or remove elements during traversal without incurring large shifts in data.
Use case: Editing live data streams, streaming event handlers.
Operation | ArrayList | LinkedList |
---|---|---|
Random Access (get/set) | O(1) | O(n) |
Add at End | O(1)* | O(1) |
Insert/Delete at Middle/Start | O(n) | O(n) / O(1) |
Memory per element | Lower | Higher (more overhead) |
Iterator-based Deletion | Slower | Faster |
*Amortized O(1), may resize when capacity is exceeded.
ArrayList
for read-heavy, index-based applications where modification is rare or done at the end.LinkedList
for write-heavy scenarios, especially where frequent insertion or removal at the start or middle occurs.ArrayList
—it's simpler and performs well in most general-purpose cases.By matching the list type to your use case, you’ll improve your program’s efficiency, readability, and memory footprint.
Java’s List
interface defines a set of common operations used to manage sequences of elements. While the interface stays the same, the performance of these operations differs significantly depending on whether the underlying implementation is an ArrayList
or a LinkedList
.
Let’s explore these operations with explanations, time complexity, and examples to help you make informed decisions based on performance.
add(E e)
– Adds element to the end.
O(1)
amortized (can be O(n)
if resize needed)O(1)
List<String> list = new ArrayList<>();
list.add("One"); // Fast, unless resizing
add(int index, E element)
– Inserts element at a specific index.
O(n)
– all subsequent elements are shiftedO(n)
– traversal to index, then O(1)
insertionlist.add(0, "Zero"); // Slower for large ArrayLists
remove(Object o)
– Removes first occurrence.
O(n)
– shifts elements after removalO(n)
– traversal, unlinking is O(1)
remove(int index)
– Removes element at index.
O(n)
– shifts elementsO(n)
– traversal, then O(1)
unlinklist.remove("One"); // Linear time for both
get(int index)
– Retrieves element at index.
O(1)
– direct index accessO(n)
– traversal from head or tailset(int index, E element)
– Replaces element at index.
O(1)
O(n)
list.set(0, "Updated"); // Fast for ArrayList
String item = list.get(2); // Instant with ArrayList, slow with LinkedList
contains(Object o)
– Checks if element exists.
O(n)
– linear searchif (list.contains("Updated")) {
System.out.println("Found!");
}
Both lists must scan through the elements linearly, as they don't use hashing like Set
.
Enhanced for-loop or Iterator:
for (String item : list) {
System.out.println(item);
}
LinkedList
.ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
if (it.next().equals("Updated")) {
it.set("Modified");
}
}
Operation | ArrayList | LinkedList |
---|---|---|
add(E) | O(1)* | O(1) |
add(index, E) | O(n) | O(n) |
remove(Object) | O(n) | O(n) |
remove(index) | O(n) | O(n) |
get(index) | O(1) | O(n) |
set(index, E) | O(1) | O(n) |
contains(Object) | O(n) | O(n) |
iterator traversal | O(n) | O(n) |
*Amortized, due to occasional resizing.
ArrayList
.LinkedList
performs better.LinkedList
for applications that depend heavily on random access.Understanding these trade-offs helps you write efficient, purpose-driven Java programs using the right List
for your specific use case.
This section demonstrates how to perform common operations—add, remove, sort, and search—on Java ArrayList
and LinkedList
using concise, runnable code examples. Each snippet is explained to reinforce both syntax and behavior.
import java.util.*;
public class ListOperationsDemo {
public static void main(String[] args) {
// Creating an ArrayList
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
// Creating a LinkedList
List<String> linkedList = new LinkedList<>();
linkedList.add("Dog");
linkedList.add("Elephant");
linkedList.add("Fox");
System.out.println("ArrayList: " + arrayList);
System.out.println("LinkedList: " + linkedList);
}
}
Explanation: add()
appends elements to the end of both ArrayList
and LinkedList
. The syntax is identical because both implement the List
interface.
arrayList.remove("Banana"); // Removes "Banana" by value
linkedList.remove(1); // Removes element at index 1 ("Elephant")
System.out.println("ArrayList after removal: " + arrayList);
System.out.println("LinkedList after removal: " + linkedList);
Explanation: remove(Object o)
deletes by value, while remove(int index)
deletes by position. These operations shift or unlink nodes depending on the list type.
Collections.sort()
and Custom Comparator// Sorting ArrayList alphabetically
Collections.sort(arrayList);
System.out.println("Sorted ArrayList: " + arrayList);
// Sorting LinkedList in reverse order using a Comparator
linkedList.sort(Comparator.reverseOrder());
System.out.println("Reverse sorted LinkedList: " + linkedList);
Explanation: Collections.sort()
sorts in natural (lexicographical) order. You can also use .sort(Comparator)
for custom ordering. Both list types support sorting via the List
interface.
// Using contains (linear search)
boolean found = arrayList.contains("Apple");
System.out.println("Contains 'Apple'? " + found); // true
// Using binarySearch (list must be sorted)
int index = Collections.binarySearch(arrayList, "Cherry");
System.out.println("'Cherry' found at index: " + index); // Index >= 0 if found
Explanation:
contains()
performs a linear search (O(n)
).binarySearch()
is faster (O(log n)
) but requires a sorted list. If the element is not found, it returns a negative insertion point.ArrayList: [Apple, Banana, Cherry]
LinkedList: [Dog, Elephant, Fox]
ArrayList after removal: [Apple, Cherry]
LinkedList after removal: [Dog, Fox]
Sorted ArrayList: [Apple, Cherry]
Reverse sorted LinkedList: [Fox, Dog]
Contains 'Apple'? true
'Cherry' found at index: 1
ArrayList
when sorting and searching are frequent, especially with large lists.LinkedList
when frequent additions/removals at ends are needed.binarySearch()
.These examples illustrate practical List
usage with operations that appear in real-world applications. You can modify and run them as-is to reinforce learning.