Question

When to Use LinkedList vs ArrayList in Java

javaarraylistcollectionslinked-list

Question

I usually write Java lists like this:

List<String> names = new ArrayList<>();

I use the List interface as the variable type so I can switch implementations later if needed.

In Java, when should LinkedList be used instead of ArrayList, and when is ArrayList the better choice?

Short Answer

By the end of this page, you will understand the practical differences between ArrayList and LinkedList in Java, including how they store data, how that affects performance, and which one is usually the better default. You will also learn the common operations where each structure performs well, the mistakes beginners make when choosing between them, and how this choice shows up in real Java codebases.

Concept

ArrayList and LinkedList are both Java implementations of the List interface, but they store data in very different ways.

ArrayList stores elements in a dynamic array.

  • Elements are kept in contiguous memory-like array slots.
  • Access by index is fast.
  • Appending to the end is usually fast.
  • Inserting or removing in the middle can be expensive because later elements must shift.

LinkedList stores elements as a chain of nodes.

  • Each node stores the value and links to neighboring nodes.
  • Access by index is slow because Java must walk through the chain.
  • Adding or removing near the ends can be efficient.
  • It has more memory overhead because each element needs node objects and links.

In real programming, the important question is not just "which one can do this operation faster in theory?" but also "what operations will my code do most often?"

As a rule of thumb:

  • Use ArrayList by default.
  • Use LinkedList only when you specifically need frequent insertions/removals at the beginning or through a list iterator, or when queue/deque-style operations are central.

Why ArrayList is usually the default:

  • Fast random access with get(index)
  • Better cache locality in practice
  • Lower memory overhead
  • Simpler and usually faster for most application code

Why LinkedList is less commonly chosen:

  • get(index) is slow: O(n)
  • Extra memory per element
  • Real-world performance is often worse than beginners expect
  • Many insert/remove scenarios still require traversal before the operation

Big-O summary:

OperationArrayListLinkedList
get(index)O(1)O(n)
add at endAmortized O(1)O(1)
add/remove at startO(n)O(1)
add/remove in middle by indexO(n)O(n) to reach node
iterationO(n), usually very fastO(n), often slower in practice

So although LinkedList has some theoretical advantages, ArrayList wins for many real applications because most code reads items, loops through them, and accesses by index more often than it constantly inserts at the front.

Mental Model

Think of ArrayList as a row of numbered lockers.

  • Every item has a position.
  • If you want item 7, you go directly to locker 7.
  • If you insert something in the middle, many later items need to shift one place.

Think of LinkedList as a treasure hunt with clues.

  • Each item tells you where the next item is.
  • To reach item 7, you must follow the chain from the start or end.
  • Removing a known item from the chain is easy: reconnect the links.

So:

  • ArrayList is better when you want fast direct access.
  • LinkedList is better when you are constantly linking and unlinking elements, especially at the ends.

Another short version:

  • ArrayList optimizes finding by position.
  • LinkedList optimizes relinking nodes once you are already there.
Take Quiz

Syntax and Examples

Both classes implement List, so you can write code against the interface.

Basic syntax

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

List<String> arrayNames = new ArrayList<>();
List<String> linkedNames = new LinkedList<>();

Example: ArrayList for general-purpose list usage

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("Ava");
        names.add("Liam");
        names.add("Noah");

        System.out.println(names.get(1));
        System.out.println(names);
    }
}

Why ArrayList fits here:

  • Adding to the end is efficient
  • Reading by index is fast
  • This is the most common style in Java applications

Example: LinkedList for queue-like behavior

import java.util.LinkedList;

public class Main {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();

        tasks.addLast("Send email");
        tasks.addLast("Generate report");
        tasks.addLast("Backup database");

        System.out.println(tasks.removeFirst());
        System.out.println(tasks);
    }
}

Why LinkedList can fit here:

  • Removing from the front is efficient
  • Adding to the back is efficient
  • It can act as a queue or deque

Important note

If you need queue behavior, prefer the queue interfaces where possible:

import java.util.ArrayDeque;
import java.util.Queue;

Queue<String> tasks = new ArrayDeque<>();

In many real projects, ArrayDeque is preferred over LinkedList for queue/deque operations.

Step by Step Execution

Consider this ArrayList example:

List<String> items = new ArrayList<>();
items.add("A");
items.add("B");
items.add("C");
items.add(1, "X");
System.out.println(items);
System.out.println(items.get(2));

Step by step:

  1. items starts empty.

    • []
  2. items.add("A")

    • A is added at the end.
    • [A]
  3. items.add("B")

    • B is added at the end.
    • [A, B]
  4. items.add("C")

    • C is added at the end.
    • [A, B, C]
  5. items.add(1, "X")

    • Insert X at index 1
    • Existing elements from index 1 onward shift right
    • Result: [A, X, B, C]
  6. System.out.println(items)

    • Prints:
[A, X, B, C]
  1. items.get(2)
    • Return the element at index 2
    • That element is B
B

Now compare with a LinkedList mental execution:

LinkedList<String> items = new LinkedList<>();
items.add("A");
items.add("B");
items.add("C");
System.out.println(items.get(2));

What happens conceptually:

  • The list stores linked nodes
  • To reach index 2, Java follows links through the list
  • It cannot jump directly like an array-backed list can

That is the key tradeoff: LinkedList may link/unlink efficiently, but indexed access is slower.

Real World Use Cases

When ArrayList is a good fit

ArrayList is the best choice for most list usage in Java applications:

  • Returning a list of users from a service
  • Storing search results
  • Holding rows from a CSV file
  • Collecting API response items
  • Looping through values for validation or display
  • Accessing items by index in UI or business logic

Example:

List<Product> products = new ArrayList<>();

This is common because most code:

  • adds items,
  • iterates over them,
  • sometimes accesses by index,
  • rarely inserts at the front repeatedly.

When LinkedList can be a good fit

Use LinkedList when your operations match its strengths:

  • Frequent addFirst() / removeFirst() operations
  • Frequent addLast() / removeLast() operations
  • Deque-like behavior
  • Insertion/removal through a ListIterator when already positioned

Example:

LinkedList<String> history = new LinkedList<>();
history.addFirst("/home");
history.addFirst("/products");
history.removeFirst();

A practical caution

If your use case is really a queue or stack, ArrayDeque is often better than LinkedList.

Example:

import java.util.ArrayDeque;
import java.util.Deque;

Deque<String> jobs = new ArrayDeque<>();
jobs.addLast("job1");
jobs.addLast("job2");
jobs.removeFirst();
Take Quiz

Real Codebase Usage

In real Java codebases, developers usually follow these patterns:

1. Default to interfaces

List<String> names = new ArrayList<>();

This keeps code flexible and makes it easier to change implementations later.

2. Default to ArrayList

Most production code uses ArrayList unless there is a clear reason not to.

Why:

  • It performs well for common workloads
  • It is easier to reason about
  • Most lists are read often and modified less dramatically than people assume

3. Use LinkedList only for specific access patterns

You may see it in code that:

  • behaves like a deque
  • adds/removes at both ends
  • manipulates elements through iterators

Example pattern:

LinkedList<String> undoStack = new LinkedList<>();
undoStack.addFirst("state1");
undoStack.addFirst("state2");
String previous = undoStack.removeFirst();

4. Prefer purpose-specific interfaces

If the collection is used as a queue:

Queue<Task> queue = new ArrayDeque<>();

If it is used as a deque:

Deque<String> deque = new ArrayDeque<>();

This expresses intent better than using List for queue behavior.

5. Avoid premature optimization

Developers often choose LinkedList because they hear inserts are fast. But if the code must first search for the insertion point, the total work may still be slow.

Example:

  • Finding the middle position: expensive in LinkedList
  • Then inserting: cheap once found
  • Overall result: often not better than ArrayList

6. Measure when performance matters

In serious performance-sensitive code, developers benchmark with realistic data rather than relying only on Big-O rules.

Common Mistakes

1. Assuming LinkedList is always faster for insertions

This is one of the most common mistakes.

Broken reasoning:

  • "Insertions are O(1), so LinkedList must be faster."

What is missed:

  • You often need to traverse the list first
  • That traversal is O(n)

2. Using LinkedList when accessing by index often

Broken example:

List<String> names = new LinkedList<>();
for (int i = 0; i < names.size(); i++) {
    System.out.println(names.get(i));
}

Why this is bad:

  • get(i) on LinkedList is O(n)
  • Repeating it in a loop can become very slow

Better:

for (String name : names) {
    System.out.println(name);
}

Or use ArrayList if indexed access is important.

3. Choosing implementation before understanding usage

Beginners sometimes ask which class is better in general.

Better question:

  • What operations happen most often?
  • Read by index?
  • Append?
  • Remove from front?
  • Iterate?

4. Using LinkedList as a queue when ArrayDeque is better

Possible but not ideal:

Queue<String> queue = new LinkedList<>();

Often better:

Queue<String> queue = new java.util.ArrayDeque<>();

5. Forgetting memory overhead

A LinkedList node stores extra links, so it usually uses more memory than ArrayList for the same elements.

6. Writing to the concrete type unnecessarily

Less flexible:

ArrayList<String> names = new ArrayList<>();

More flexible:

List<String> names = new ArrayList<>();

This does not mean you should switch implementations casually, but it keeps your code less coupled.

Comparisons

ArrayList vs LinkedList

FeatureArrayListLinkedList
Backing structureDynamic arrayDoubly linked nodes
Best forGeneral-purpose list usageFrequent add/remove at ends
Access by indexFastSlow
Append to endUsually fastFast
Insert at beginningSlowFast
Remove from beginningSlowFast
Memory usageLowerHigher
Iteration performanceUsually betterOften worse
Default choiceYesRarely

LinkedList vs ArrayDeque

Use caseLinkedListArrayDeque
Queue operationsWorksUsually preferred
Deque operationsWorksUsually preferred
Random accessPoorNot supported as a list
Memory/performance for queue useOften worseUsually better

List interface vs concrete implementation

Declaration styleExampleWhy use it
Interface typeList<String> names = new ArrayList<>();Flexible, less coupled
Concrete typeArrayList<String> names = new ArrayList<>();Use only when concrete methods matter

Quick decision guide

If you mostly...Prefer
Read by indexArrayList
Append items and iterateArrayList
Add/remove from the front oftenLinkedList or ArrayDeque
Use queue/deque behaviorArrayDeque
Need a general defaultArrayList
Take Quiz

Cheat Sheet

List<String> a = new ArrayList<>();
List<String> b = new LinkedList<>();

Choose ArrayList when

  • You need get(index) often
  • You mostly append items
  • You iterate a lot
  • You want the usual default choice

Choose LinkedList when

  • You frequently add/remove at the start or end
  • You need deque-like operations
  • You are using a ListIterator to insert/remove while traversing

Performance summary

  • ArrayList.get(i) -> O(1)
  • LinkedList.get(i) -> O(n)
  • ArrayList.add(end) -> amortized O(1)
  • LinkedList.addFirst() -> O(1)
  • LinkedList.removeFirst() -> O(1)
  • Insert/remove in middle often requires traversal first

Best default

  • Use List<T> list = new ArrayList<>();

Better for queues/deques

  • Prefer ArrayDeque over LinkedList

Common warning

This loop is bad for LinkedList:

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

Prefer:

for (String item : list) {
    System.out.println(item);
}

FAQ

Should I use ArrayList or LinkedList by default in Java?

Use ArrayList by default. It is usually faster and more memory-efficient for common application code.

Is LinkedList ever better than ArrayList?

Yes. It can be a better fit when you frequently add or remove elements at the beginning or end, or when you use it as a deque.

Why is LinkedList.get(index) slow?

Because a linked list cannot jump directly to an index. It must follow links from one node to the next until it reaches that position.

Is LinkedList good for queues?

It works, but ArrayDeque is usually a better choice for queue and deque behavior.

Why do many developers write List<String> x = new ArrayList<>();?

Because programming to the interface keeps code less coupled to one implementation and easier to change later.

Is Big-O enough to choose between them?

No. Big-O is useful, but real performance also depends on memory layout, traversal costs, and actual usage patterns.

Does LinkedList use more memory than ArrayList?

Yes, usually. Each linked node stores extra references in addition to the element value.

When should I declare LinkedList<String> directly instead of List<String>?

Only when you need methods specific to LinkedList, such as addFirst() or removeLast().

Take Quiz

Mini Project

Description

Build a small Java program that models a task processor and compares two kinds of collection usage: a general task list and a queue of pending jobs. This project demonstrates that ArrayList is a strong default for ordinary list storage, while queue-like operations are better expressed with a queue/deque structure rather than forcing everything into one list type.

Goal

Create a program that stores tasks in an ArrayList, processes waiting jobs with a deque, and prints the results clearly.

Requirements

  • Create a general task list using the List interface with an ArrayList implementation.
  • Add at least four task names and print one task by index.
  • Create a pending job queue using ArrayDeque.
  • Add at least three jobs to the queue and process them from the front.
  • Print the remaining contents of both collections at the end.
Take Quiz

Keep learning

Related questions

View more