Java List, ArrayList, and LinkedList
Build and edit an ordered reading list one operation at a time. Learn when indexes shift, when to copy instead of sharing edits, and how ArrayList and LinkedList behave differently behind the same List contract.
Before you start: review typed collections and wrapper classes and for loops. Each runner contains a complete Java 21 Main.java program, needs no input, and uses the existing isolated sandbox.
What you will be able to do
- Distinguish inserting an entry from replacing one.
- Apply the correct index bounds to reads and insertions.
- Search and remove deliberately, including duplicate entries.
- Traverse a list without repeatedly searching linked nodes by index.
- Explain aliases, shallow copies, and mutation ownership.
- Build an ordered reading-list update without editing the original.
1. Add a new slot, or replace an existing value?
Think of a reading list as a row of numbered cards. Appending puts a new card at the end. Inserting puts a new card between existing cards and pushes the later cards along. Replacing writes a different title on a card that is already present. These are three different intentions, even though each involves a new title.
Trace the example statement by statement. After the first two calls, Java is at index 0 and SQL at index 1. add(1, "HTML") inserts before SQL, so SQL moves to index 2. set(1, "CSS") replaces HTML and returns the old value. It does not insert another card, so the size remains three.
Choose add when the number of entries should grow, and set when one existing entry should change. Position is meaningful, but it is not a permanent identity: adding or removing an earlier entry changes later indexes. If your application needs a stable book ID, store that as data rather than using a changing list index as the ID.
Append, insert, then replace
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Inserted: [Java, HTML, SQL]
Replaced: HTML
Now: [Java, CSS, SQL]
Count: 3
Trace the positions without running
- Start: [Java, SQL], size 2.
- Insert HTML at 1: [Java, HTML, SQL], size 3.
- Replace index 1 with CSS: [Java, CSS, SQL], still size 3.
Change the insertion to index zero and predict all positions again. A position labels the current sequence, not a permanent identity.
2. Read indexes and insertion positions have different bounds
For a list of size n, get, set, and remove-by-index require 0 <= index < n. Insertion with add(index, value) allows 0 <= index <= n. The extra position n means ‘append after the last entry’. For an empty list, insertion at zero is valid, but reading at zero is not.
The example first inserts at size(), which is zero. It then reads the newly created entry at zero. Reading at size(), now one, fails because there is still only one entry. The catch is included so you can see the invalid request clearly; do not use exceptions as the normal way to discover whether a list is empty.
Before reading the first or last item, ask whether an item exists. If (!reading.isEmpty()), index zero and index size() - 1 are valid for that unchanged list. A prior bounds check is not a concurrency guarantee: other threads could still modify an ordinary list. This lesson uses single-threaded code and does not promise thread safety.
An insertion position is not always a readable index
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Empty: true
First: Java
No entry at index size().
3. Searching returns a position, or tells you there is no match
A reading list may include the same title twice because the student plans to revisit it. contains answers a yes/no question, indexOf returns the first match, and lastIndexOf returns the last. For these String titles, matching uses String value equality and is case-sensitive: Java and java are not the same title.
When a search returns -1, it means no matching entry was found. It is a sentinel, not a readable position. Never immediately call get(indexOf(title)) unless you have already proved a match exists. The example branches on the returned position, printing a useful message instead of trying to read index -1.
If you only need membership, contains expresses that intention directly. If you need the entry position, search once and save the result instead of performing contains and indexOf in succession. On ordinary unsorted lists, looking for a value may require examining many entries. Lists do not become fast key-based lookup structures simply because they are generic.
Search results and the missing-value sentinel
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Contains SQL: true
First Java: 0
Last Java: 2
Python is not scheduled.
4. Removing a value does not mean removing every duplicate
remove("Java") removes the first equal title and returns whether it found one. With [Java, SQL, Java], the second Java remains. remove(0) instead removes the entry at position zero and returns that removed entry. After each removal, later positions close the gap; there is no empty hole left behind.
Notice the return types in the example: a boolean describes whether a matching value was removed, while the index overload returns a String. If the element type is Integer, remember the previous lesson: a primitive int argument selects the position overload, whereas Integer.valueOf(value) can select value removal.
Removing several matches while walking forward by index is easy to get wrong. An element can shift into the slot you just processed, then be skipped when the loop increments. The repair exercise makes that mistake visible with adjacent duplicates. Iterator-based removal and collection algorithms receive their own later lesson; here we repair a small ArrayList loop by walking backward.
Remove one matching value or one position
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Removed match: true
Remaining: [SQL, Java]
Removed position: SQL
Remaining: [Java]
Missing match: false
5. Read all entries with a sequential traversal
When the question is ‘show every title’, an enhanced for loop expresses it without managing a read index. The example uses a LinkedList through the List interface. A separate display counter numbers the output starting at one; it is a presentation number, not a call to get on each iteration.
An ArrayList can read an indexed entry directly. A LinkedList must navigate nodes to reach an indexed entry, starting from the nearer end. Repeatedly calling linked.get(i) for every position can accumulate quadratic work. An enhanced for loop traverses sequentially instead, so it avoids repeatedly searching for the next index from an end.
Keep this traversal read-only. Adding or removing entries through the list while an enhanced for loop is using its iterator is unsafe and may cause ConcurrentModificationException. That exception is best-effort bug detection, not a reliable program-control mechanism or a concurrency lock. Build a separate result or use an appropriate mutation API instead.
Number a linked reading list in one pass
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
1. Java
2. SQL
3. HTML
6. Decide who owns changes to the list
Assigning alias = original copies a reference, not the entries. Both variables point to the same list, so a replacement through alias is visible through original. By contrast, new ArrayList<>(original) creates a separate container holding the current element references. Adding to that copy does not add to the original.
This is a shallow copy. In our example the elements are immutable Strings, so there is no editable title object shared between the containers. If the elements were mutable Book objects, modifying a Book through one list could still be visible through the other list. Copying a container is not the same as cloning every object it refers to.
Method names and documentation should make mutation ownership explicit. A helper called previewReadingList can reasonably return a changed copy while leaving its borrowed input alone. Such a helper also works with an unmodifiable List.of input. The lab follows that design and validates all titles instead of relying on a particular source implementation.
An alias shares edits; a copied container does not
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Original: [CSS, SQL]
Alias: [CSS, SQL]
Copy: [Java, SQL, HTML]
7. LinkedList also supports a queue-like contract
LinkedList implements Deque as well as List. A Deque describes operations at both ends. In the example, addLast schedules work at the back, addFirst inserts an urgent setup task, and pollFirst retrieves and removes the next task. Using a Deque variable advertises end-based work rather than indexed reading.
pollFirst returns null when the deque is empty, while removeFirst throws NoSuchElementException. Although LinkedList permits null elements, avoid inserting null into this queue example: it would make a returned null ambiguous between an actual queued null and an empty queue. Null policy remains a design decision, not a benefit of permitting every possible value.
This example demonstrates a second contract, not a claim that LinkedList is always the best queue. Different Deque implementations have different tradeoffs. For ordinary indexed reading lists, ArrayList is a sensible starting point; for endpoint-only workflows, select a Deque implementation based on those operations and measured needs. Avoid choosing solely because ‘linked means faster insertion’ sounds convincing.
Use LinkedList through a Deque contract
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Next: Setup
Waiting: [Java, SQL]
Empty poll: null
8. Compare operations, not just class names
ArrayList stores references in a resizable array. Indexed get and set are constant-time operations. Appending is amortized constant time: growth occasionally requires extra work. Inserting or removing near the front generally shifts later references. Its capacity is storage room; its size is the number of actual entries, as the final example demonstrates.
LinkedList links nodes together. End operations need no shifting of later entries, but reaching an arbitrary index requires traversal. Relinking at a node is cheap only after you have reached that node; add(index, value) still has a location cost. Each node also carries link overhead. Neither class is universally faster, and actual performance includes allocation and memory-access effects.
For the lab, use ArrayList because the task builds an ordered result, removes one matching title, and appends a title. The list is small, clarity matters, and there is no evidence that node-based storage improves it. Do not add stopwatch claims to these tiny examples: reliable performance comparisons need representative workloads and proper benchmarking beyond this lesson.
Capacity is room, not a count of entries
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Size before add: 0
Size after add: 1
First: Java
Repair exercise: the duplicate that survived removal
Remove every SQL entry from this small ArrayList. The starter appears reasonable but leaves a duplicate behind. Trace the indexes after the first removal, then repair the traversal without editing the loop body’s matching rule.
Main.java · Find the skipped title
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Current result:
[SQL, Java]
Reveal the explained repair
After the forward loop removes index zero, the next SQL shifts from one to zero. Incrementing i skips it. Walking backward means unvisited lower indexes stay valid after a removal. This repairs correctness for this ArrayList exercise, but repeated removals can still be costly. A later lesson covers iterator and bulk-filtering approaches.
Main.java · Walk backward over removals
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
[Java]
Independent lab: preview an updated reading list
Create a preview of a student’s reading plan. Trim surrounding whitespace from every title, remove the first title matching a completed book, then append the next book. Preserve remaining order and duplicates. The source is borrowed and must stay unchanged, including when validation fails.
- Implement previewReadingList(List<String> source, String completed, String nextTitle).
- Reject a null list, or any null/blank source, completed, or next title with IllegalArgumentException.
- Normalize titles using String.strip; matching is case-sensitive after normalization.
- Remove only the first matching completed title; a missing match removes nothing.
- Append the normalized next title, even when it is already present.
- Return a distinct editable List<String>; never modify the source or its whitespace.
The starter compiles but prints nothing. Implement the helper and call it to produce this output. Spaces in the original list are deliberately preserved:
Preview: [SQL, Java basics, Collections]
Original: [ Java basics , SQL, Java basics]
Editable result: [SQL, Java basics, Collections, Practice]
Main.java · Build a reading-list preview
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stage 1 hints
Stage 1: validate before relying on data. Check the source reference. Write requireTitle to reject null or stripped-empty Strings, then return the stripped title. Validate both operation arguments too.
Stage 2 hints
Stage 2: build a new ArrayList. Traverse the borrowed source with an enhanced for loop, normalize each entry, and add it to the result. A late invalid entry must not leave the original partly changed.
Stage 3 hints
Stage 3: apply the requested operations to the copy. Use remove(String) for the first equal title, then append the next title. Return the result and test both editable and unmodifiable inputs.
Reveal the complete explained solution
Main.java · Normalize a copy, preserve the source
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
All edits happen in a new ArrayList, so the source retains its spacing, order, and entries even when a later title is invalid. remove receives a String and removes one matching value, not all duplicates. If no match exists, its false result is intentionally ignored by this contract. The returned container is editable and independent; the String elements are immutable. This is a single-threaded preview, not a snapshot guarantee against concurrent source changes.
Test your contract
- Empty source produces a one-entry result containing the next title.
- Adjacent completed duplicates lose only the first match.
- A missing completed title leaves all source titles in order before the append.
- Null and whitespace-only titles are rejected without modifying the source.
- ArrayList, LinkedList, and List.of sources produce equivalent previews; editing the result never edits the source.
To test a null element, add it to an ArrayList explicitly. List.of rejects null before the helper is called. For rejection tests, catch IllegalArgumentException and print the source afterward to confirm that validation did not edit it.
Common mistakes and how to diagnose them
set does not create a new slot
An initially empty ArrayList has nothing to replace. Use add to insert an entry before calling set on its index. Supplying initial capacity does not create entries either.
Using -1 as an index after searching
A missing indexOf result must be handled as no match. Branch before get or set. A search sentinel is not a readable position.
Skipping adjacent matches during removal
Trace which entry shifts into the removed position. For the small ArrayList repair, traverse backward. Do not assume an exception will detect a logically skipped value.
An unexpected change to the original
Check whether the supposed copy is actually an alias. Assignment shares a list reference; new ArrayList<>(source) creates a separate container, but remains a shallow element copy.
LinkedList gets slow in an indexed loop
Repeated get(i) calls repeatedly navigate nodes. For sequential reading, prefer enhanced for instead of interpreting List as a promise of constant-time indexed access.
Mutation during enhanced for
Do not add or remove through the underlying list during iterator traversal. Fail-fast exceptions are best-effort diagnostics. Use a separate result now; learn iterator and bulk operations in their dedicated lesson.
Frequently asked questions
Does List sort values automatically?
No. A list maintains a sequence according to insertions and explicit edits. Our helper preserves remaining order; it does not alphabetize or deduplicate. Sorting is a separate operation covered later.
Why does add(size(), value) work but get(size()) fail?
Insertion can create a new last slot after the current final entry. Reading needs an entry that already exists, so the highest readable index is size() - 1. An empty list has no readable index.
Is LinkedList always faster at insertion?
No. Finding an arbitrary insertion position can require traversal. End operations and edits through an already positioned cursor have different costs from add(index, value). Select based on the actual workload, not just the word insertion.
Can a List.of source be used in the lab?
Yes. The helper only reads the source and changes its own ArrayList. It therefore does not depend on source mutation support. A null-entry test must use a container that permits null because List.of rejects null at construction.
Does strip remove every visually blank character?
No. strip follows Java’s Unicode whitespace definition through Character.isWhitespace. Not every character that looks like spacing is included, such as non-breaking space. The lab defines blankness by strip; broader text-cleaning rules need an explicit domain policy.
Should every helper return an editable copy?
No. Some methods intentionally mutate an owned list, others return an unmodifiable result. The important point is to document the contract. This lab specifically promises a new editable preview without changing borrowed input.
Make positions, ownership, and operations explicit
Insertion grows a sequence; replacement does not. Removal shifts positions, and searches can return no match. ArrayList and LinkedList share an interface but not the same access costs. A copied container separates structural edits; it does not deep-clone mutable elements.
Before moving on, explain why the forward removal skipped a title and why the lab can accept an unmodifiable source. Next: Set implementations, where uniqueness becomes part of the collection contract.
Can you edit and traverse a list deliberately?
Answer six questions and read the explanations. Quiz answers are temporary and do not save course progress.
References and related learning
This original SovranCode lesson uses the official Java 21 APIs to verify list behavior and implementation tradeoffs.
Review collections and generics · Review arrays · Java course roadmap.
Last updated: