JavaCollectionsintermediate
Updated:

List Interface in Java: ArrayList, LinkedList and Vector

6 min read

The List interface defines ordered, index-based collections in Java. Learn its core methods and how ArrayList, LinkedList and Vector implement them differently.

TL;DR – Quick Answer

The List interface in java.util represents an ordered collection that allows duplicates and gives you index-based access. Its three classic implementations are ArrayList (resizable array, fast random access), LinkedList (doubly-linked list, fast insertion at the ends) and Vector (legacy, synchronized ArrayList). For most day-to-day work, ArrayList is the default choice.

On This Page

When an interviewer asks "what is a List in Java?", they are not testing whether you can recite a definition. They want to know if you understand the contract an interface defines and whether you can pick the right implementation for a workload. This page gives you both: the List contract, and how ArrayList, LinkedList and Vector fulfil it in very different ways.

If you have not yet seen the big picture of Iterable → Collection → List/Set/Queue, skim the Java Collections Framework overview first — this page zooms into one branch of that hierarchy.

What the List interface guarantees

java.util.List extends Collection and adds three guarantees that a plain Collection does not make:

  1. Insertion order is preserved. Elements stay in the order you added them.
  2. Duplicates are allowed. list.add("Java") twice gives you a list of size 2.
  3. Index-based access. You can call get(2), set(2, x), add(2, x) and remove(2) using positions, exactly like an array.

That third point is the real differentiator. A Set also stores elements, but it has no concept of "the element at position 4". List does.

Because List is an interface, you always instantiate a concrete class:

List<String> skills = new ArrayList<>();   // the default choice
List<String> queue  = new LinkedList<>();  // when you mostly add/remove at ends
List<String> legacy = new Vector<>();      // legacy, avoid in new code

Declaring the variable as List<String> instead of ArrayList<String> is called programming to the interface. It lets you change the implementation in one place without touching any method that consumes the list.

Core methods you will use every day

These come up constantly in real code and in machine-coding rounds:

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

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

        stack.add("Java");          // append at end
        stack.add("Spring Boot");
        stack.add(1, "SQL");        // insert at index 1, shifts the rest right

        System.out.println(stack.get(0));        // Java
        System.out.println(stack.indexOf("SQL")); // 1
        System.out.println(stack.contains("React")); // false

        stack.set(2, "Spring");     // replace, does NOT shift anything
        stack.remove("SQL");        // remove by value (first match)
        stack.remove(0);            // remove by index

        System.out.println(stack);  // [Spring]
        System.out.println(stack.size()); // 1
    }
}

Notice the two overloads of remove: remove(int index) and remove(Object o). With a List<Integer>, list.remove(1) removes the element at index 1, not the value 1 — to remove the value you must write list.remove(Integer.valueOf(1)).

Common mistake: A common mistake beginners make is calling list.remove(i) on a List<Integer> expecting value-based removal. Java picks the remove(int) overload, deletes by index, and the bug silently corrupts your data. Use remove(Integer.valueOf(i)) when you mean the value.

ArrayList: the default implementation

ArrayList stores elements in a plain Object[] array that grows by roughly 50% when it fills up. That single design decision explains its entire performance profile:

  • get(i) and set(i, x) are O(1) — direct array indexing.
  • add(x) at the end is amortized O(1) — occasionally a resize copies the array.
  • add(i, x) and remove(i) in the middle are O(n) — elements shift.
  • Memory is compact and cache-friendly, because elements sit next to each other.

For the full story of the growth mechanics, capacity management and iteration rules, read ArrayList internal working and operations.

LinkedList: the doubly-linked alternative

LinkedList stores each element in its own node with prev and next references. It implements both List and Deque, so it can act as a stack or a queue:

  • addFirst(x), addLast(x), removeFirst(), removeLast() are all O(1).
  • get(i) is O(n) — the list walks node by node from the nearest end.
  • Removal via an iterator that is already positioned is O(1).
  • Each element costs extra memory for the node object and two references.

In practice, LinkedList wins in far fewer cases than most tutorials suggest. The head-to-head numbers are in ArrayList vs LinkedList.

Vector: the legacy synchronized List

Vector predates the Collections Framework (it shipped with Java 1.0) and was retrofitted to implement List. It behaves like an ArrayList with two differences:

  1. Every method is synchronized, so single operations are thread-safe but slower.
  2. It doubles capacity on resize instead of growing by 50%.

The synchronization sounds attractive until you realize it does not protect compound operations. This classic check-then-act sequence is still broken with Vector:

// Broken even though Vector methods are synchronized:
if (!vector.contains(x)) {   // thread B can add x right here
    vector.add(x);            // now you have a duplicate
}

Two threads can interleave between contains and add. You would need external locking anyway — at which point Vector's per-method locks are pure overhead.

Interview note: If asked "is Vector thread-safe?", the strong answer is: "Each method is synchronized, but compound operations like check-then-add still need external synchronization, so Vector gives a false sense of safety. Modern code uses Collections.synchronizedList or CopyOnWriteArrayList instead."

Performance comparison at a glance

Operation ArrayList LinkedList Vector
get(i) / set(i, x) O(1) O(n) O(1) + lock
add(x) at end Amortized O(1) O(1) Amortized O(1) + lock
add(0, x) at head O(n) O(1) O(n) + lock
add(i, x) in middle O(n) O(n) to find + O(1) to link O(n) + lock
remove(i) in middle O(n) O(n) to find + O(1) to unlink O(n) + lock
contains(x) O(n) O(n) O(n) + lock
Memory per element Low High (node + 2 refs) Low
Thread safety No No Per-method only

Note the LinkedList middle-insert row: linking the node is O(1), but reaching index i is O(n). Total cost is still linear, which surprises many candidates.

Iterating and sorting a List

Iteration and sorting come up in nearly every practical task. Here is a runnable example covering the three iteration styles plus sorting:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

public class ListIteration {
    public static void main(String[] args) {
        List<Integer> scores = new ArrayList<>(List.of(72, 95, 61, 88));

        // 1. for-each: cleanest for read-only passes
        for (int s : scores) {
            System.out.print(s + " ");
        }
        System.out.println();

        // 2. Iterator: the ONLY safe way to remove while iterating
        Iterator<Integer> it = scores.iterator();
        while (it.hasNext()) {
            if (it.next() < 70) {
                it.remove();           // safe removal
            }
        }
        System.out.println(scores);    // [72, 95, 88]

        // 3. Index-based: when you need the position
        for (int i = 0; i < scores.size(); i++) {
            System.out.println(i + " -> " + scores.get(i));
        }

        scores.sort(Comparator.reverseOrder());
        System.out.println(scores);    // [95, 88, 72]
    }
}

Run this and try replacing it.remove() with scores.remove(...) inside the loop — you will get a ConcurrentModificationException. The list's fail-fast iterator detects that the list changed behind its back.

Pro tip: For simple "remove everything matching a condition" jobs, skip the iterator entirely: scores.removeIf(s -> s < 70) does the same thing in one line and is the idiomatic modern form.

Immutable lists: List.of and List.copyOf

Since Java 9, List.of(...) creates compact immutable lists. They throw UnsupportedOperationException on any mutation and reject null elements. Use them for constants and safe return values:

List<String> tracks = List.of("Java", "Testing", "DevOps");
// tracks.add("AI");  // throws UnsupportedOperationException

Arrays.asList(...) is the older cousin with a trap: it is fixed-size but not immutable — set works, add throws. When you need a mutable list from literals, write new ArrayList<>(List.of(...)).

Which one should you pick?

  • Default: ArrayList. Fast reads, compact memory, amortized O(1) appends.
  • Heavy add/remove at both ends, or you need a Deque: LinkedList — though ArrayDeque usually beats it for pure queue/stack work.
  • Vector: only when maintaining legacy code that already uses it.
  • Concurrent reads with rare writes: CopyOnWriteArrayList.

This decision is one of the most common questions in Java collections interview rounds, usually followed by "why?" — and the table above is your answer. If you are building these fundamentals as part of a bigger goal, the structured path in our Java Full Stack course covers collections with the same interview-first approach.

Frequently Asked Questions

What is the difference between List and ArrayList in Java?
List is an interface that defines the contract for ordered collections, while ArrayList is a concrete class that implements that contract using a resizable array. You cannot instantiate List directly; you write List<String> names = new ArrayList<>() so you can swap implementations later without changing the rest of your code.
Does a List in Java allow duplicate elements?
Yes. Allowing duplicates is one of the defining features of List, along with insertion order and index-based access. If you need to reject duplicates, use a Set implementation such as HashSet instead.
Is Vector still used in modern Java code?
Rarely. Vector synchronizes every method, which makes it slow, and its coarse-grained locking still does not make compound operations safe. New code uses ArrayList, and when thread safety is needed, Collections.synchronizedList or CopyOnWriteArrayList are the standard picks.
Which List implementation should I use by default?
ArrayList. It has O(1) random access, good cache locality and low memory overhead. Only switch to LinkedList when your workload is dominated by insertions and removals at the head or via an iterator, which is uncommon in typical application code.
Can I add or remove elements from a List created with List.of()?
No. List.of() returns an immutable list, and calling add, remove or set on it throws UnsupportedOperationException. If you need a mutable copy, wrap it: new ArrayList<>(List.of(1, 2, 3)).
How do I make a List thread-safe?
Wrap it with Collections.synchronizedList(list) for general use, or use CopyOnWriteArrayList when reads massively outnumber writes. Avoid Vector in new code; it exists mainly for backward compatibility.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us