LinkedList vs ArrayList in Java: Differences and When to Use

LinkedList vs ArrayList in Java compared on memory, time complexity and real use cases, plus ArrayDeque advice and how to convert between them.

ArrayList storing four references in one contiguous array compared with LinkedList storing four separate nodes linked by next and prev references

In Java, ArrayList stores elements in a resizable array, while LinkedList stores each element in a separate node linked to the previous and next nodes. The array gives ArrayList constant-time access by index and compact memory, so it is the right default in most LinkedList vs ArrayList decisions. LinkedList is faster only for adding or removing at the front or through an iterator, and for a queue or a stack, ArrayDeque fits better than either of them.

The following example runs the same operations on both classes and notes the cost of each line.

List<String> jobs = new ArrayList<>(List.of("report", "invoice", "photo"));
LinkedList<String> queue = new LinkedList<>(jobs);
String third = jobs.get(2);                                  // "photo", reads one array slot
String thirdToo = queue.get(2);                              // "photo", walks the nodes
queue.addFirst("urgent");                                    // links one node, no shifting
jobs.add(0, "urgent");                                       // shifts 3 elements to the right
String next = queue.pollFirst();                             // "urgent", LinkedList is also a Deque
ArrayList<String> converted = new ArrayList<>(queue);        // [report, invoice, photo]

Both lists give the same results, only at a different cost. We look at how each class stores elements, compare the cost of every common operation, cover the cases where LinkedList still fits, and convert between the two.

1. How ArrayList and LinkedList Store Elements

An ArrayList keeps references to its elements in one Object[] array, next to each other in memory. A LinkedList is a doubly linked list, so it wraps every element in a Node object that holds the element and references to the previous and next nodes, and the list itself only remembers the first and last node.

ArrayList storing four references in one contiguous array compared with LinkedList storing four separate nodes linked by next and prev references
ArrayList reaches index 2 in one step, while LinkedList follows links from the nearest end

The node class inside the JDK is a small static class with three fields.

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
}

Each node is a separate object with its own header. On a 64-bit HotSpot JVM with compressed references, which is the default for heaps under 32 GB, a node typically takes 24 bytes, whereas one array slot takes 4 bytes. A list of one million elements therefore spends about 24 MB on nodes in a LinkedList, compared with about 4 to 6 MB for the backing array of an ArrayList, before counting the elements themselves.

Both classes implement List, keep insertion order, allow duplicates and null, and are not synchronized. LinkedList also implements Deque, so it has queue methods such as offerFirst(), pollLast() and peekFirst().

2. Time Complexity Compared

The cost of each operation follows from the storage. The array makes index access cheap and inserting in the middle expensive, and the nodes make the ends cheap and everything that needs an index expensive.

OperationArrayListLinkedList
get(i), set(i, e)O(1)O(n), walks from the closer end
add(e) at the endO(1) amortizedO(1)
addFirst(e), add(0, e)O(n), shifts all elementsO(1)
add(i, e) in the middleO(n), shifts the tailO(n), walks to i and links in O(1)
removeFirst()O(n), shifts all elementsO(1)
removeLast()O(1)O(1)
remove(i), remove(Object)O(n)O(n), the search dominates
Iterator.remove() during a loopO(n) per callO(1) per call
contains(), indexOf()O(n)O(n)
Memory per elementOne array slot plus spare capacityOne node object with two links

Removing or inserting in the middle is O(n) for both classes, because LinkedList must first walk to the position. The often-quoted O(1) insert for LinkedList applies only when we already stand at the position, i.e. at an end or on the current element of an iterator.

3. Why ArrayList Wins in Practice

Big O counts steps, but not what each step costs. Walking a LinkedList means following references to nodes spread across the heap, and each jump can miss the CPU cache. Shifting elements in an ArrayList is a single System.arraycopy() call over contiguous memory, which modern CPUs do very fast.

A LinkedList also creates one object per element, so it puts more work on the garbage collector, and it has no spare capacity to reuse. For these reasons, ArrayList is faster for most real workloads, even for inserts in the middle, because a LinkedList must walk the nodes to reach the position first. When performance matters, we measure both with JMH on our own data instead of relying on rules of thumb.

One pattern is slow with LinkedList no matter what. An index loop calls get(i) for every element, and each call walks the nodes, so the loop becomes O(n2).

static int totalLength(List<String> names) {
    int total = 0;
    for (String name : names) {        // iterator, O(n) for both classes
        total += name.length();
    }
    return total;
}
int fast = totalLength(new LinkedList<>(List.of("report", "invoice")));   // 13

Say a reporting service receives a List of 100,000 rows from a library that returns a LinkedList. An old for (int i = 0; …) loop with get(i) walks about 2.5 billion nodes in total, because each call starts again from the closer end. Switching to the enhanced for loop, as in totalLength(), makes the loop linear for every List implementation.

4. When LinkedList Is the Right Choice

LinkedList fits when we add and remove at both ends of a list, or remove many elements while walking a long list with an iterator. In both cases, no element shifts and no index lookup is needed.

LinkedList<String> pending = new LinkedList<>(List.of("report", "draft", "invoice", "draft"));
Iterator<String> it = pending.iterator();
while (it.hasNext()) {
    if (it.next().equals("draft")) {
        it.remove();                                         // unlinks one node, O(1)
    }
}
String left = pending.toString();                            // "[report, invoice]"

For bulk removal, ArrayList.removeIf() closes the gaps in one pass, so it is also linear. The LinkedList advantage is left for loops that make other decisions per element, such as inserting a new element with ListIterator.add() next to the current one.

As a queue or a stack, ArrayDeque is the better default. It is likely to be faster than LinkedList when used as a queue, because it uses a circular array instead of nodes. We keep LinkedList when we need null elements, which ArrayDeque rejects, or one object that is both a List and a Deque.

Deque<String> printer = new ArrayDeque<>();
printer.offerLast("report");
printer.offerLast("photo");
printer.offerFirst("urgent");
String printing = printer.pollFirst();                       // "urgent"
String after = printer.peekFirst();                          // "report"

A print server is the typical case. Jobs join at the tail, urgent jobs go to the head, and the printer always takes the head, so every operation touches an end and neither class needs an index.

5. Converting a LinkedList to an ArrayList

Both classes have a constructor that takes any Collection, so converting is one line. The constructor copies the elements in iteration order, and the two lists are independent afterwards.

LinkedList<String> linked = new LinkedList<>(List.of("report", "invoice", "photo"));
ArrayList<String> array = new ArrayList<>(linked);                         // [report, invoice, photo]
ArrayList<String> filled = new ArrayList<>();
boolean copied = filled.addAll(linked);                                    // true, [report, invoice, photo]
ArrayList<String> upper = linked.stream().map(String::toUpperCase).collect(Collectors.toCollection(ArrayList::new));   // [REPORT, INVOICE, PHOTO]
List<String> readOnly = List.copyOf(linked);                               // [report, invoice, photo], unmodifiable

We use addAll() when the target list already exists, a stream when we transform elements on the way, and List.copyOf() when nobody should change the result. The reverse direction works the same way.

ArrayList<String> source = new ArrayList<>(List.of("report", "invoice"));
LinkedList<String> back = new LinkedList<>(source);                        // [report, invoice]
LinkedList<String> reversedCopy = new LinkedList<>(source.reversed());     // [invoice, report], Java 21

A common reason to convert is an API that returns a LinkedList, such as an older DAO, while our code reads elements by index. Copying once into an ArrayList costs O(n) and makes every later get(i) O(1).

6. Choosing Between ArrayList and LinkedList

For most code, the decision takes a few seconds. We start with ArrayList and switch only when the access pattern matches one of the rows below.

Access patternPick
Read by index, append at the end, loop over all elementsArrayList
Sort, binary search or pass to APIs that index the listArrayList
Queue or stack without null elementsArrayDeque
Add and remove at both ends and also need List methodsLinkedList
Insert or remove next to the current element while iterating a long listLinkedList with ListIterator
Several threads share the listA synchronized wrapper or CopyOnWriteArrayList

Neither class is thread-safe, so the last row applies to both, and the tutorial on synchronizing an ArrayList shows the options. The List interface overview compares the remaining implementations.

7. LinkedList vs ArrayList FAQs

Developers comparing the two classes, in interviews or in code reviews, ask about speed, memory and queues.

7.1. Which Is Faster, ArrayList or LinkedList?

ArrayList for almost every workload. It reads by index in constant time, and its contiguous memory makes loops and shifts fast. LinkedList is faster only for repeated adds and removes at the head of a long list or through an iterator.

7.2. Why Is LinkedList Rarely Used?

Because ArrayList covers list use cases with less memory and ArrayDeque covers queue use cases faster. That leaves LinkedList for the few cases that need both a List and a Deque in one object or null elements in a queue.

7.3. Which Uses More Memory?

LinkedList. Every element needs its own node with two extra references and an object header, whereas ArrayList needs one array slot per element plus some spare capacity, which trimToSize() can release.

7.4. Does LinkedList Have a Capacity Like ArrayList?

No. LinkedList allocates one node per element, so it has no capacity, no ensureCapacity() and no resizing. It grows and shrinks one node at a time.

7.5. Is LinkedList Thread-Safe?

No, neither class is. For a shared queue, a ConcurrentLinkedDeque or a BlockingQueue such as LinkedBlockingQueue is the right tool, and for a shared list we use Collections.synchronizedList() or CopyOnWriteArrayList.

8. Conclusion

ArrayList keeps elements in one array and LinkedList in linked nodes. That one difference makes index access O(1) for ArrayList and O(n) for LinkedList, and it makes the ends of a LinkedList cheap to change.

In practice, ArrayList wins most comparisons because it uses less memory and works well with CPU caches. We keep LinkedList for iterator-based edits of long lists and for list-plus-deque use, choose ArrayDeque for queues and stacks, and convert between the classes with one constructor call whenever an API hands us the other type.

9. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.