Java ArrayList

In the last lesson you learned about the Java List interface. Now let’s get hands-on with the one you will reach for most often: the ArrayList. It is like an array, but it can grow and shrink as you add and remove items, and it comes packed with helpful methods.

🤔 Why ArrayList over a plain array?

You are building a to-do list app. You don’t know how many tasks the person will type. A plain array makes you guess the size up front, and that guess is almost always wrong.

Here is what an ArrayList gives you over a raw array:

  • It resizes itself, so you never run out of slots.
  • It ships with ready-made methods to add, remove, search, and count.
  • It keeps items in the exact order you put them in.
  • It prints in a clean, readable form for quick debugging.

Think of a plain array as a parking lot with a fixed number of painted spots. Once full, no more cars. An ArrayList is a valet who keeps finding room and reshuffles cars as needed.

🧩 Creating an ArrayList

You create an ArrayList with new, tell it what type it holds in the angle brackets, and import it first. The line below makes an empty list that holds only String values.

import java.util.ArrayList;
ArrayList<String> names = new ArrayList<>(); // ✅ an empty list of Strings

The <String> part is a generic. It tells the collection what type lives inside. So ArrayList<String> means “a list of Strings”, and Java blocks you from adding a number by mistake. The empty <> on the right is fine, because Java reads the type from the left.

The list starts empty, with size 0. Let’s put things in.

Use wrapper types, not primitives

For numbers, use the wrapper type inside the brackets: ArrayList<Integer>, not ArrayList<int>. Collections hold objects, and int, double, and boolean are not objects. So use Integer, Double, and Boolean instead. Java quietly converts between int and Integer for you, which is called autoboxing.

➕ Adding and reading items

You add items with add and read them back by index with get. Indexes start at 0, just like arrays. The program below adds three names, reads one back, checks the count, and prints the list.

import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("Alex"); // ✅ lands at index 0
names.add("Riya"); // ✅ lands at index 1
names.add("Arjun"); // ✅ lands at index 2
System.out.println(names.get(0)); // read the item at index 0
System.out.println("Size: " + names.size());
System.out.println(names); // print the whole list
}
}

Line by line:

  • add appends each name to the end, so they land at indexes 0, 1, and 2.
  • get(0) reads the first item, “Alex”.
  • size() reports the count, which grew to 3 on its own.
  • Printing the list shows every item inside square brackets.

You never declared a size. The list handled all of that.

Output

Alex
Size: 3
[Alex, Riya, Arjun]

A second form of add takes a position. add(index, item) inserts at that spot and pushes everyone after it one place right. The snippet below squeezes a new name into the middle.

ArrayList<String> names = new ArrayList<>();
names.add("Alex"); // [Alex]
names.add("Arjun"); // [Alex, Arjun]
names.add(1, "Riya"); // ✅ insert at index 1
System.out.println(names);

Now “Riya” sits at index 1, and “Arjun” slides from index 1 to index 2 to make room.

Output

[Alex, Riya, Arjun]

✏️ Updating and removing

You change an item with set and remove one with remove. Removing from the middle shifts everyone after it left to close the gap, and the list handles that for you. The program below replaces one name, then removes two in different ways.

import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("Alex");
names.add("Riya");
names.add("Arjun");
names.set(1, "Riya Sharma"); // ✅ replace whatever is at index 1
names.remove("Alex"); // ✅ remove by value (the String "Alex")
names.remove(0); // ✅ remove by index (now "Riya Sharma")
System.out.println(names);
}
}

What each line does:

  • set(1, "Riya Sharma") overwrites the item at index 1. It replaces, not inserts.
  • remove("Alex") finds the matching value, pulls it out, and shifts the rest left.
  • remove(0) removes by index instead, taking out whatever sits at index 0.

Only “Arjun” is left. So remove takes either a value or an index. That is handy, but a classic trap with number lists, covered in the mistakes section.

Output

[Arjun]

🔁 Looping over an ArrayList

You loop over an ArrayList much like an array. There are three common ways.

The most common is the for-each loop. Use it when you just want the values, not positions. The program below sums three scores.

import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> scores = new ArrayList<>();
scores.add(85);
scores.add(90);
scores.add(78);
int total = 0;
for (int score : scores) { // ✅ for-each walks every value
total = total + score;
}
System.out.println("Total: " + total);
}
}

The for-each visits every item in order. We add each to a running total. We declared ArrayList<Integer>, and Java unwraps each Integer into an int automatically.

Output

Total: 253

The second way is the index loop. Reach for it when you need the position. It uses size() as the stopping point and get(i) to read each item.

for (int i = 0; i < scores.size(); i++) { // ✅ when you need the index
System.out.println("Item " + i + ": " + scores.get(i));
}

The third way is the iterator, a small helper that walks the list one item at a time and lets you remove the current item safely. We see why that matters next.

🧹 Removing safely while looping

Do not change the size of a list while a for-each is walking it. If you do, Java throws a ConcurrentModificationException, which means “you changed the list while I was reading it”. The code below looks fine but crashes.

ArrayList<String> names = new ArrayList<>();
names.add("Alex");
names.add("Riya");
names.add("Arjun");
for (String name : names) {
if (name.equals("Riya")) {
names.remove(name); // ❌ throws ConcurrentModificationException
}
}

The for-each is built on a hidden iterator that expects the list to stay still. Calling names.remove directly makes it notice the change and refuse to go on.

There are two clean fixes. The first uses the iterator yourself and calls its own remove, so the walk stays in sync.

import java.util.ArrayList;
import java.util.Iterator;
ArrayList<String> names = new ArrayList<>();
names.add("Alex");
names.add("Riya");
names.add("Arjun");
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.equals("Riya")) {
it.remove(); // ✅ safe removal through the iterator
}
}
System.out.println(names);

Here it.remove() removes the item the iterator just handed you, keeping iterator and list in agreement. No crash.

The second fix is shorter and what you will reach for most: removeIf with a condition. The line below removes every name equal to “Riya”.

names.removeIf(name -> name.equals("Riya")); // ✅ short and safe
System.out.println(names);

removeIf takes a test and removes every item that passes. It handles the safe removal internally, so you don’t think about iterators.

Output

[Alex, Arjun]

A simple rule

If you only read during a loop, a for-each is perfect. The moment you need to remove while looping, switch to removeIf or an iterator’s own remove. Never call the list’s remove from inside a for-each.

🔍 Other handy methods

ArrayList has more methods you will use all the time. They read like English and save you from writing loops by hand.

  • contains(x) returns true if the item is in the list.
  • indexOf(x) returns the index of an item, or -1 if it is not there.
  • isEmpty() returns true if the list has no items.
  • size() returns the current count of items.
  • clear() removes everything and leaves an empty list.

The program below shows a few of them in action.

ArrayList<String> names = new ArrayList<>();
names.add("Alex");
System.out.println(names.contains("Alex")); // true
System.out.println(names.indexOf("Alex")); // 0
System.out.println(names.isEmpty()); // false
names.clear();
System.out.println(names.isEmpty()); // true

So contains checks membership, indexOf finds a position, isEmpty checks for nothing inside, and clear empties it. These keep your code short and clear.

Output

true
0
false
true

🛠️ Worked example: a to-do list

Let’s pull it together in a tiny to-do list. We add tasks, mark one done by removing it, drop tasks that contain a certain word, then print what is left and how many remain.

import java.util.ArrayList;
public class TodoApp {
public static void main(String[] args) {
ArrayList<String> todos = new ArrayList<>();
todos.add("Buy groceries");
todos.add("Reply to Alex");
todos.add("Pay electricity bill");
todos.add("Call Riya");
// Finished one task, so remove it by value.
todos.remove("Buy groceries"); // ✅
// Drop anything that mentions a person we will handle tomorrow.
todos.removeIf(task -> task.contains("Alex")); // ✅ safe removal
// Add a new task at the top so it shows first.
todos.add(0, "Morning standup"); // ✅ insert at index 0
System.out.println("Tasks left:");
for (int i = 0; i < todos.size(); i++) {
System.out.println((i + 1) + ". " + todos.get(i));
}
System.out.println("Total remaining: " + todos.size());
}
}

What happens:

  • We start with four tasks in the order we added them.
  • remove("Buy groceries") takes out that task and shifts the rest left.
  • removeIf(... contains("Alex")) drops “Reply to Alex” safely, no crash.
  • add(0, "Morning standup") slots a new task at the very front.
  • The index loop prints each task with a friendly number, and size() reports the count.

Output

Tasks left:
1. Morning standup
2. Pay electricity bill
3. Call Riya
Total remaining: 3

⚙️ How it grows inside (and why it’s fast)

You don’t need this to use an ArrayList, but it makes the performance notes click.

  • Inside, an ArrayList is a plain array, the backing array, that secretly stores your items.
  • It tracks the size (how many items you have) and the capacity (how many slots the array offers).
  • When you add and a slot is free, it drops the item in. Quick.
  • When the array fills up, the list makes a bigger one, copies everything across, and keeps going.
  • It grows in big jumps, usually about half again as large, so copying happens rarely.

Spread across many adds, the average cost of one add stays tiny. That idea is called amortized cost. In plain terms, adding to the end is cheap.

🚦 Performance notes

Which operations are fast comes down to that backing array:

  • Reading or writing by index with get and set is very fast. It jumps straight to the slot.
  • Adding to the end with add is fast on average, thanks to the growth trick.
  • Inserting or removing in the middle is slower, because every item after that spot shifts by one.

So an ArrayList shines when you mostly read by position and add at the end. It is less ideal for constant inserts or deletes near the front of a large list. That last point is where a LinkedList differs, our next lesson.

⚠️ Common Mistakes

A few ArrayList slip-ups catch nearly everyone.

First, using a primitive type inside the brackets.

ArrayList<int> nums = new ArrayList<>(); // ❌ will not compile
ArrayList<Integer> nums = new ArrayList<>(); // ✅ use the wrapper type

Collections hold objects, and int is not an object, so you must use Integer.

Second, confusing remove(index) with remove(value) on a number list.

ArrayList<Integer> nums = new ArrayList<>();
nums.add(10);
nums.add(20);
nums.add(30);
nums.remove(2); // ❌ removes index 2, so 30 goes
nums.remove(Integer.valueOf(20)); // ✅ removes the value 20

A plain int like 2 calls remove(int index) and is treated as a position. To remove by value, wrap it with Integer.valueOf(...) so Java picks remove(Object).

Third, removing while a for-each is running.

for (String name : names) {
names.remove(name); // ❌ ConcurrentModificationException
}
names.removeIf(name -> true); // ✅ safe one-liner instead

Fourth, an autoboxing surprise with null. Reading a null value into an int makes Java unbox null and throw a NullPointerException.

ArrayList<Integer> nums = new ArrayList<>();
nums.add(null);
int x = nums.get(0); // ❌ NullPointerException on unbox
Integer y = nums.get(0); // ✅ keep it as Integer, then check

So when a list might hold null, read into the wrapper type first and check before unboxing.

✅ Best Practices

Habits that keep your ArrayList code clean.

  • Reach for ArrayList when the size changes. It is the default resizable list.
  • Always declare the element type, like ArrayList<String> or ArrayList<Integer>. Never leave the brackets empty on the left.
  • Use the built-in methods. contains, indexOf, size, and isEmpty save you from manual loops.
  • Loop with for-each when you only read values. Use an index loop when you need positions.
  • Remove during a loop only with removeIf or an iterator’s own remove, never with the list’s remove inside a for-each.
  • For number lists, remember remove(int) is by index and remove(Integer.valueOf(x)) is by value.

🧩 What You’ve Learned

Nicely done. Let’s recap ArrayList.

  • ✅ An ArrayList is a resizable list that grows and shrinks automatically.
  • ✅ Use add to append, add(index, item) to insert, get to read, set to update, and remove to delete.
  • size() gives the count, and you can loop with for-each, an index loop, or an iterator.
  • ✅ Remove safely while looping with removeIf or an iterator’s remove, never the list’s remove in a for-each.
  • ✅ Use wrapper types like Integer inside the brackets, not primitives.
  • ✅ Inside it is a backing array that grows in jumps, so get and end-add are fast while middle inserts are slower.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What is the main advantage of an ArrayList over an array?

    Why: An ArrayList grows and shrinks on its own, unlike a fixed-size array.

  2. 2

    How do you add an item to an ArrayList?

    Why: add() appends an item to the end of the list.

  3. 3

    What type should you use for a list of numbers?

    Why: Collections hold objects, so use the wrapper type Integer, not the primitive int.

  4. 4

    What does the size() method return?

    Why: size() gives the current count of items in the ArrayList.

🚀 What’s Next?

ArrayList is great for most lists, but there is another List type with different strengths: the LinkedList. It is faster at adding and removing from the ends. Let’s compare them next.

Java LinkedList

Share & Connect