Array List ADT, Dynamic Resizing, Position-Based Lists, and Iterators
CS205 Data Structures
Arrow keys to navigate
An abstract, ordered collection of elements
A List stores elements in a linear sequence. Each element has an index from 0 to n-1.
| Operation | Description |
|---|---|
get(i) | Return element at index i |
set(i, e) | Replace at index i |
add(i, e) | Insert at i, shift right |
remove(i) | Remove at i, shift left |
size() | Number of elements |
People in a numbered line. Insert someone at position 3 → everyone behind scoots back one spot.
The List ADT is abstract — it defines what operations exist, not how they work. Both ArrayList and LinkedList implement the same interface!
A dynamic array that grows as needed
An ArrayList stores elements in a contiguous backing array:
size ≤ capacity always. When size == capacity, the array must be resized before adding more.
Valid indices: 0 to size-1. Accessing beyond size throws IndexOutOfBoundsException even if the backing array has unused slots!
The superpower of array-based lists
Go directly to arr[i] — no searching needed!
Go directly to Room 302 — no checking rooms 1, 2, 3... Address arithmetic computes the memory location instantly.
O(1) random access is the main reason to use ArrayList. Read-heavy workloads love arrays.
Making room costs O(n) in the worst case
add(0, e) shifts ALL n elements. Only add(size, e) avoids shifting.
Filling the gap costs O(n) in the worst case
Setting the old last slot to null prevents the array from referencing an object the user thinks was removed.
Why doubling gives amortized O(1) appends
If grew by +1 each time: total copies = 1+2+3+...+n = O(n²).
Doubling: total copies = 1+2+4+...+n = O(n).
Amortized O(1) per append!
Doubling wastes up to 50% memory. Growth factor 1.5 wastes less but still O(1) amortized.
When and how to reclaim unused memory
If grow at 2x and shrink at 1/2, alternating add/remove near the boundary triggers resize every single operation. O(n) per op!
Leaving a buffer zone between grow (full) and shrink (1/4 full) guarantees many operations between resizes, preserving amortized O(1).
A stable handle immune to shifting
Every insert/delete can invalidate stored indices. If you saved "index 2 = C", that reference breaks after any modification before it.
A Position holds onto an element regardless of where it sits. Insertions and deletions don't break other positions.
Index = "person in seat #3" — changes when people move.
Position = "Alice's name tag" — always refers to Alice.
A doubly-linked list where each node IS a position
Doubly-linked list with sentinel header & trailer nodes that simplify edge cases.
| Operation | Time |
|---|---|
addBefore(p, e) | O(1) |
addAfter(p, e) | O(1) |
remove(p) | O(1) |
before(p) / after(p) | O(1) |
first() / last() | O(1) |
Header and trailer are dummy nodes — never hold real data. Eliminates null-checking for first/last operations.
Pointer rewiring instead of element shifting
Only 4 pointer assignments per insert, 2 per remove — O(1). No elements shifted. This is the fundamental advantage over ArrayList for positional operations.
Traverse a collection without knowing its implementation
Press "next channel" without knowing if signals come via cable, satellite, or streaming. The remote = iterator — uniform traversal regardless of source.
Iterators decouple traversal logic from data structure details. Code using iterators works with ANY Iterable.
Choose the right implementation for your workload
| Operation | ArrayList | Node List (Doubly Linked) |
|---|---|---|
get(i) / index access | O(1) | O(n) |
set(i, e) | O(1) | O(n) |
add(0, e) / insert front | O(n) | O(1) |
add(n, e) / append | O(1)* | O(1) |
| Insert/delete at position | O(n) | O(1) if you have position |
| Memory per element | 1 ref | 3 refs (elem+prev+next) |
| Cache performance | Excellent | Poor (scattered) |
Node List insert/delete is O(1) only if you already have the position. Finding a position by value or index still costs O(n).
ArrayList = numbered bookshelf (instant find, slow insert).
Node List = chain of paperclips (easy to add/remove, slow to find #47).
How ArrayList and LinkedList fit into the bigger picture
Both ArrayList and LinkedList implement List<E> — code written against the interface works with either. Choose the implementation based on your workload.
Object[] arrayRandomAccess markerDequeRandomAccess — indexing is O(n)Java's LinkedList does NOT expose Position objects. Our lecture's positional list ADT is more powerful — positions enable O(1) insert/delete at known locations.
Interactive decision helper — answer the questions
What is your primary access pattern?
When in doubt, use ArrayList. Cache performance dominates in practice. Even middle insertions are often faster with ArrayList on modern hardware.
How many element moves happen in this sequence?
Count the total number of individual element moves (shifts) across all three operations.
Mistakes that bite data structures students
Always use the iterator's remove(), or build a separate "to-remove" list first.
When removing multiple elements by index, work backwards (high→low) so shifts don't affect remaining indices.
This addAfter method for a doubly-linked node list has a bug — find and fix it
Everything on one slide
| Operation | ArrayList | Node List |
|---|---|---|
| Index access | O(1) | O(n) |
| Insert at ends | O(1)*/O(n) | O(1) |
| Insert at position | O(n) | O(1) |
| Memory/element | Low | High |
| Cache locality | Great | Poor |
ArrayList = a numbered bookshelf — finding book #47 is instant, but inserting means sliding everything over. Node List = a chain of paperclips — easy to clip/unclip anywhere, but finding the 47th clip means counting from the start.
Starting with capacity 2, how many times does the array resize during 10 addLast operations?
An ArrayList starts with capacity = 2 and size = 0. We perform 10 consecutive addLast() calls. The array doubles when full.
After each resize, what is the new capacity? When is the next resize triggered?
Test your understanding — 3 questions
Q1: What is the amortized cost of addLast() on an ArrayList that doubles when full?
Q2: In a Node List with sentinels, what are the first() and last() positions?
Q3: Why does ArrayList have better cache performance than LinkedList?
What does the ArrayList contain after these operations?
What happens when we run this code?