skip to content

What are the Collection bulk operations (addAll, removeAll, retainAll, containsAll, clear), and what set-like semantics do they implement?

level: middleimportance: should knowfreq 60%

answer

  1. addAll=union, removeAll=difference, retainAll=intersection
  2. containsAll=subset test (read-only)
  3. clear=empty, returns void
  4. mutators return boolean 'did it change?'
  5. pass a Set to removeAll/retainAll for O(1) contains

basics

~20 s

They operate on whole collections at once: addAll adds everything from another collection (union), removeAll deletes everything also in another (difference), retainAll keeps only what's in another (intersection), containsAll checks if all elements are present (subset), and clear empties the collection.

solid answer

~50 s

Collection declares five bulk operations that act on an entire other collection in one call, each mirroring a set operation. addAll(c) adds all of c's elements (a union, in-place) and returns true if this collection changed. removeAll(c) removes every element also present in c (a relative complement / difference). retainAll(c) keeps only elements that are also in c, removing the rest (an intersection). containsAll(c) tests whether this collection contains every element of c (a subset test) and returns a boolean without mutating. clear() removes everything. The three mutating ones (addAll/removeAll/retainAll) return a boolean indicating whether the collection was modified, which is useful in loops. They can throw UnsupportedOperationException on immutable collections. A classic gotcha: removeAll/retainAll repeatedly call contains on the argument, so passing a List with O(n) contains makes them O(n*m) - pass a Set for performance.

code

java · 16 lines
java
List<Integer> a = new ArrayList<>(List.of(1, 2, 3, 4));
List<Integer> b = List.of(3, 4, 5);

new ArrayList<>(a) { { addAll(b); } };      // union view -> [1,2,3,4,3,4,5]

List<Integer> diff = new ArrayList<>(a);
diff.removeAll(b);                          // difference -> [1, 2]

List<Integer> inter = new ArrayList<>(a);
inter.retainAll(b);                         // intersection -> [3, 4]

boolean subset = a.containsAll(List.of(2, 3)); // true (read-only)

// Performance: make the argument a Set for big removeAll/retainAll
Set<Integer> bSet = new HashSet<>(b);
inter.retainAll(bSet);                       // contains() is O(1)

go deeper

for a junior

Knows the five methods exist and roughly what each does (add many, remove many, keep common, check all present, empty).

for a middle

Maps each to a set operation, knows mutators return a changed-boolean, and that containsAll is read-only.

for a senior

Articulates the O(n*m) contains() performance trap and the Set-argument fix, plus UnsupportedOperationException on immutable collections.

for a principal

Reasons about when to use bulk ops vs streams for clarity/perf, the AbstractCollection default-implementation behavior, and concurrency implications of bulk mutation on shared collections.

## What "bulk operations" means Most `Collection` methods touch **one** element (`add(e)`, `remove(e)`, `contains(e)`). The **bulk operations** instead take **another whole collection** as the argument and act on all of its elements at once. There are five, and four of them map directly onto **set algebra** (think Venn diagrams). `this` is the collection you call the method on; `c` is the argument. ### 1. `boolean addAll(Collection<? extends E> c)` - UNION Adds every element of `c` into `this`. For a `List` it appends (duplicates fine); for a `Set` it adds only the new ones. Returns `true` if `this` changed as a result. ```java a.addAll(b); // a becomes a ∪ b ``` ### 2. `boolean removeAll(Collection<?> c)` - DIFFERENCE Removes from `this` **every element that is also in `c`** (relative complement, `this \ c`). Returns `true` if anything was removed. ```java a.removeAll(b); // a becomes a − b ``` ### 3. `boolean retainAll(Collection<?> c)` - INTERSECTION Keeps in `this` **only** the elements that are **also in `c`**, removing all others (`this ∩ c`). Returns `true` if `this` changed. ```java a.retainAll(b); // a becomes a ∩ b ``` ### 4. `boolean containsAll(Collection<?> c)` - SUBSET TEST Returns `true` if `this` contains **every** element of `c` (i.e. `c ⊆ this`). It is **read-only** - it never mutates. ```java if (a.containsAll(b)) { /* b is a subset of a */ } ``` ### 5. `void clear()` - EMPTY Removes **all** elements, leaving `this` empty (size 0). Returns nothing. ## The boolean return value The three **mutating** bulk ops (`addAll`, `removeAll`, `retainAll`) return a **`boolean`** that answers *"did this collection change?"*. `false` means the operation had no effect (e.g. `retainAll` when everything was already retained). This is handy for fixed-point loops. ## Exceptions to expect - **`UnsupportedOperationException`** - the mutating ops throw this on **immutable** collections (e.g. `List.of(...)`, `Collections.unmodifiableList(...)`). - **`NullPointerException`** - if `c` is `null`, or contains `null` and `this` forbids nulls. - **`ClassCastException`** - if element types are incompatible with `this` (rare). ## Performance gotcha (the interview favorite) `removeAll(c)` and `retainAll(c)` work by iterating `this` and calling **`c.contains(element)`** for each one. If `c` is a **`List`**, `contains` is **O(n)**, so the whole operation is **O(n*m)**. If `c` is a **`HashSet`**, `contains` is **O(1)**, so it drops to **O(n)**. Rule of thumb: *the argument to removeAll/retainAll should be a Set when it's large.* Also, `list.removeAll(list)` is just a slow `clear()`, and a subtle case: `AbstractCollection`'s default `removeAll` may iterate `this`, so removing while iterating relies on the iterator's `remove`. ## Quick mapping | Method | Set op | Mutates? | Returns | |---|---|---|---| | `addAll` | union | yes | changed? | | `removeAll` | difference | yes | changed? | | `retainAll` | intersection | yes | changed? | | `containsAll` | subset test | no | boolean | | `clear` | empty | yes | void |

  • Why might list.removeAll(otherList) be unexpectedly slow?
    removeAll calls otherList.contains() for each element of list. A List's contains() is O(n), making it O(n*m). Convert otherList to a HashSet to get O(1) contains and O(n) overall.
  • What does the boolean returned by retainAll mean?
    It is true if the collection was modified (some elements removed), false if everything was already in the argument and nothing changed.

saying these in an interview costs you the question

  • Confusing removeAll (difference) with retainAll (intersection)
  • Thinking containsAll mutates - it does not
  • Ignoring the O(n*m) cost when the argument is a List
  • Assuming bulk ops always succeed - they throw UnsupportedOperationException on immutable collections

context