Removing strings that contain a target substring.
Combine substring detection with safe removal to clean lists of Strings or text fields.
A common AP-style task: given an ArrayList<String> (or a list of objects with text fields), delete every entry whose String contains a given substring. The pattern blends two earlier skills: indexOf-based detection and backward-iteration removal.
Substring matching tests for a sequence of characters anywhere in the target — not for an exact match. "unhappy".indexOf("happy") returns 2, indicating the substring appears starting at index 2.
On the AP exam, this pattern appears in text-cleaning FRQs: filter chat messages, remove banned words, delete entries with a flagged tag.
indexOf for detection; backward loop for removal.
Two patterns combined into one method.
Detecting a substring
if (text.indexOf(target) != -1) {
// substring is present somewhere
}
indexOf returns the position of the first occurrence, or -1 when the substring is absent. The != -1 check is the standard "contains" test in AP CSA scope.
The full template
public void removeContaining(ArrayList<String> list, String target) {
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).indexOf(target) != -1) {
list.remove(i);
}
}
}
Backward iteration is essential because each removal shifts later elements left.
For object fields
If the list holds objects with a text accessor, swap list.get(i) for list.get(i).getName() (or whichever accessor returns the text):
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).getName().indexOf(target) != -1) {
list.remove(i);
}
}
Case sensitivity
indexOf is case-sensitive: "Hello".indexOf("hello") returns -1. AP problems usually want exact-case matching. If the prompt asks for case-insensitive, normalize both Strings to lowercase first.
Empty target string
"".indexOf("any string") returns 0 — the empty string is considered present at the start of every String. Most AP problems don't ask about this case, but watch for it if the prompt allows empty targets.
Vocabulary for text filtering.
Used in any "remove containing" or "filter by text" problem.
- Substring — a contiguous sequence of characters within a String.
- Contains — a String includes the target substring anywhere.
- indexOf — returns the position of the substring, or
-1. - Case sensitivity — whether
'A'and'a'are treated as the same character. - Normalize — converting strings to a uniform form (e.g., lowercase) before comparison.
- Backward removal — the safe pattern for deleting during a loop.
How substring removal is tested.
Contains-vs-equals confusion and missing backward iteration dominate.
What FRQ graders check:
- Contains, not equals. The prompt says "contains" — use
indexOf, not.equals(). - Backward iteration. Removal during a forward loop skips entries.
- Correct sentinel.
indexOfreturns-1when absent. - Right field. For object lists, call the accessor named in the prompt.
MCQ patterns: tracing the list after a removal pass; identifying which entries match the contains condition.
Remove every banned message.
Trace the backward loop and the contains check.
Problem. Remove every message that contains the word "spam".
public void filterSpam(ArrayList<String> messages) {
for (int i = messages.size() - 1; i >= 0; i--) {
if (messages.get(i).indexOf("spam") != -1) {
messages.remove(i);
}
}
}
Input. ["hello there", "buy spam now", "spam offer", "good morning", "no spammers here"].
Trace backward.
- i=4: "no spammers here".
indexOf("spam")= 3 (matches inside "spammers"). Remove. List:["hello there", "buy spam now", "spam offer", "good morning"]. - i=3: "good morning".
indexOf("spam")= -1. Keep. - i=2: "spam offer".
indexOf("spam")= 0. Remove. List:["hello there", "buy spam now", "good morning"]. - i=1: "buy spam now".
indexOf("spam")= 4. Remove. List:["hello there", "good morning"]. - i=0: "hello there".
indexOf("spam")= -1. Keep.
Final. ["hello there", "good morning"].
Why this matters. "no spammers here" was caught even though "spam" wasn't the whole word — that's the contains behavior. If the prompt wanted exact-word matching, you'd use .equals() or extra logic; "contains" is broader.
Substring-removal pitfalls.
Most come from picking the wrong matching test or loop direction.
- Using
.equals()when the prompt says "contains". Misses entries where the substring is part of a larger word. - Forward loop with
remove(i). Skips entries — the classic safe-removal bug. - Comparing
indexOfresult with== 0. That only matches when the substring is at the very start. Use!= -1. - Case-sensitivity mismatch. Searching for "SPAM" won't match "spam" without normalization.
- Using
==on Strings. Always useindexOfor.equals(). - Wrong field on objects. Call
getName()when that's the relevant text accessor; don't calltoString()unless specified.
Substring removal at a glance.
The right test plus the right loop.
Substring Removal Reference
AP Quick Reference| Prompt phrase | Test | AP Exam Tip |
|---|---|---|
| "contains the substring" | indexOf(target) != -1 |
Matches anywhere in the String. |
| "equals the value" | .equals(target) |
Exact match only. |
| "starts with" | indexOf(target) == 0 |
Match at the front. |
| Loop direction | Backward | Safe for removal. |
| Case-insensitive | Lowercase both before comparing | Only when the prompt requires. |
How to lock in substring removal.
Pick the right test for the prompt; pair it with the backward loop.
Use the three practice tools below this lesson in order:
- MCQ Practice — for each option, read the prompt's verb: "contains" →
indexOf; "equals" →.equals(). Then verify backward iteration. - Java Lab — write methods that remove entries containing "@", "spam", or any other substring. Test with edge cases like empty Strings and exact-word matches.
- FRQ Practice — chain the contains test with the backward removal loop. If the list holds objects, call the right accessor on each one.
If you can match "contains" → indexOf → backward loop instantly, this entire problem class is solved.
Practice — attempt these now.
AP-style assessments aligned to this lesson. Time them.