Skip to content

Latest commit

 

History

History
1267 lines (844 loc) · 47.9 KB

File metadata and controls

1267 lines (844 loc) · 47.9 KB

Java String Code Review Exercises

Audience: adult learners who need to review, read, and approve Java code (including code generated by AI agents) — not necessarily write it from scratch.

Format: each exercise gives a spec (what the code is supposed to do), an example, and a code snippet. Answer the question, then click Answer to check yourself and read the explanation.

Assumptions (unless an exercise says otherwise):

  • Arguments are non-null.
  • The code compiles and is being reviewed for behavioral correctness.
  • "Java 11+" or "Java 15+" is noted next to exercises that use newer String APIs.

There are 50 exercises across 8 topics. Most snippets are correct; some contain common String-handling mistakes for you to catch.


Section 1: Reference Equality vs. Value Equality

Exercise 1

Spec: Return true if the two greetings have the same text content ("hello").

public static boolean sameGreeting(String input) {
    String a = "hello";
    String b = new StringBuilder("hel").append("lo").toString();
    return a == b;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — == compares object references, and b was built with StringBuilder, so it's a different object even though the text matches. It should use .equals().
  • C. No — StringBuilder.append mutates the original string, so b no longer equals "hello".
  • D. No — string literals in Java are case-sensitive, so "hello" and "hel" + "lo" are different values.
Answer

Answer: B== on Strings checks object identity, not content. new StringBuilder(...).toString() always creates a brand-new String object, so a == b is false even though both hold "hello". Use a.equals(b).

Exercise 2

Spec: Return true if two strings have the same content.

public static boolean sameGreeting(String a, String b) {
    return a.equals(b);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — equals() only checks whether the two references point to the same object, not the content.
  • C. No — equals() throws an exception whenever the two strings have different lengths.
  • D. No — equals() is case-insensitive by default, so "Hi" and "hi" would incorrectly be treated as equal.
Answer

Answer: AString.equals() compares character content, which is exactly what's needed here. (B describes ==, not equals(); C and D are fabricated behaviors.)

Exercise 3

Spec: Return true, since both variables are assigned the same string literal.

String a = "cat";
String b = "cat";
boolean same = (a == b);

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — since a and b are different variables, == always compares references and returns false.
  • C. No — comparing Strings with == requires calling .intern() explicitly first, or the code won't compile.
  • D. No — Java only pools single-character string literals in the string constant pool, not longer ones like "cat".
Answer

Answer: A — Identical string literals are interned by the compiler into the same object in the string constant pool, so a and b refer to the same object and == correctly returns true. This only holds for compile-time literals/constants, not for strings built at runtime (see Exercise 1).

Exercise 4

Spec: Return true if two strings are equal, using compareTo.

public static boolean areEqual(String a, String b) {
    return a.compareTo(b) == 1;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — compareTo() ignores case, so it can't reliably detect equal strings.
  • C. No — compareTo() returns 0 when the strings are equal, not 1; this should check == 0.
  • D. No — compareTo() only compares the first character of each string.
Answer

Answer: CcompareTo() returns 0 for equal strings, a negative number if a comes before b, and a positive number if a comes after b. Checking == 1 will almost always be false, even for equal strings.

Exercise 5

Spec: Return true if two strings are equal, ignoring case.

public static boolean sameWord(String a, String b) {
    return a.equalsIgnoreCase(b);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — equalsIgnoreCase() requires both strings to be the same length or it throws an exception.
  • C. No — equalsIgnoreCase() only ignores case for the first letter of each string.
  • D. No — equalsIgnoreCase() compares references, like ==, so it returns false unless a and b are the same object.
Answer

Answer: AequalsIgnoreCase() is exactly the right tool for a case-insensitive content comparison.

Exercise 6

Spec: Return true if both strings hold the same content, or are both null; must not throw NullPointerException.

public static boolean sameOrBothNull(String a, String b) {
    return java.util.Objects.equals(a, b);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — Objects.equals() still throws a NullPointerException if either argument is null, just like calling .equals() directly.
  • C. No — Objects.equals() only works when comparing two null values; it can't compare two non-null strings.
  • D. No — Objects.equals() performs reference comparison (like ==) rather than checking string content.
Answer

Answer: AObjects.equals(a, b) is null-safe: it returns true if both are null, false if only one is null, and otherwise delegates to a.equals(b).


Section 2: String Immutability

Exercise 7

Spec: Append " world" to name and return the updated string.

public static String greet(String name) {
    name.concat(" world");
    return name;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — concat() only works with StringBuilder objects, not String, so this fails to compile.
  • C. No — concat() mutates the string in place, but only the first call in a method is remembered.
  • D. No — Strings are immutable in Java; concat() returns a new String rather than modifying name in place, so the returned value is unchanged. It should be name = name.concat(" world");.
Answer

Answer: D — Every String method that looks like it "modifies" the string (concat, trim, toUpperCase, replace, substring, ...) actually returns a new String and leaves the original untouched. The result has to be captured, usually by reassigning it.

Exercise 8

Spec: Append " world" to name and return the updated string.

public static String greet(String name) {
    name = name.concat(" world");
    return name;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — reassigning name inside the method also changes the variable in the caller, causing unexpected side effects.
  • C. No — concat() throws an exception when passed a string literal.
  • D. No — since Strings are immutable, this code will not compile.
Answer

Answer: A — Reassigning the local variable name to the new concatenated string, then returning it, is the correct fix for the immutability issue in Exercise 7.

Exercise 9

Spec: Return the uppercase version of a string, leaving the original string unchanged.

public static String shout(String s) {
    return s.toUpperCase();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — toUpperCase() modifies s in place, so the caller's original string is unexpectedly changed too.
  • C. No — toUpperCase() without a Locale argument throws an exception starting in Java 11.
  • D. No — this returns void since Strings are immutable, so the method fails to compile.
Answer

Answer: AtoUpperCase() returns a new uppercase String and never modifies the original, which is exactly what the spec asks for.

Exercise 10

Spec: Remove all spaces from a string.

public static String removeSpaces(String s) {
    return s.replace(" ", "");
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — replace() only removes the first occurrence of the target substring, so multiple spaces will remain.
  • C. No — replace() treats its first argument as a regular expression, so a literal space won't be matched correctly.
  • D. No — replace() returns void, so this method actually returns null.
Answer

Answer: AString.replace(CharSequence, CharSequence) replaces all literal occurrences (unlike replaceAll, which uses regex — see Exercise 50). This correctly strips every space.

Exercise 11

Spec: Remove the first character of the string "in place" so the caller sees the change.

public static void removeFirstChar(String s) {
    s = s.substring(1);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — Java passes the reference by value; reassigning the local parameter s has no effect on the caller's variable, and since the method returns nothing, the change is lost entirely.
  • C. No — substring(1) actually removes the last character, not the first.
  • D. No — substring() throws an exception whenever the string is longer than one character.
Answer

Answer: B — There's no way to modify a String "in place" from inside a method in Java — Strings are immutable, and reassigning a parameter only changes the local copy of the reference. The method must return the new string, and the caller must capture it.

Exercise 12

Spec: Return true, since combined and literal represent the exact same compile-time constant text.

public static boolean check() {
    final String part = "wor";
    String combined = "Hello " + part + "ld";
    String literal = "Hello world";
    return combined == literal;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — string concatenation with + always creates a new object at runtime, so == must return false here.
  • C. No — final local variables are stored on the stack, so they can never be part of the string constant pool.
  • D. No — comparing a concatenated result to a literal with == always throws an exception unless new String(...) was used.
Answer

Answer: A — Because part is a final variable initialized with a compile-time constant, the expression "Hello " + part + "ld" is itself a compile-time constant. The compiler folds it into "Hello world" and interns it in the same pool as the literal, so == returns true. (Without final, or with a non-constant value, this would not hold — that's the case in Exercise 1.)


Section 3: Substrings & Indexing

Exercise 13

Spec: Return the last 3 characters of a string (length ≥ 3). Example: "hello""llo".

public static String lastThree(String s) {
    return s.substring(s.length() - 3, s.length() - 1);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — substring() cannot be called with two arguments in modern Java; only a single starting index is supported.
  • C. No — substring()'s end index is exclusive, so using s.length() - 1 excludes the final character. It should be s.length().
  • D. No — the start index is inclusive of one extra character, so it should be s.length() - 2.
Answer

Answer: Csubstring(begin, end) includes begin but excludes end. s.substring(s.length() - 3, s.length() - 1) only grabs 2 characters (e.g., "ll" from "hello"), not 3. The fix is s.substring(s.length() - 3, s.length()), or equivalently s.substring(s.length() - 3).

Exercise 14

Spec: Return the last 3 characters of a string (length ≥ 3).

public static String lastThree(String s) {
    return s.substring(s.length() - 3);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — substring() with a single argument returns everything up to that index, not from it, so this returns the first length - 3 characters instead of the last 3.
  • C. No — this throws an exception whenever the string's length is exactly 3.
  • D. No — substring() always allocates a full copy of the original string internally, causing a memory leak.
Answer

Answer: Asubstring(begin) (single-argument form) returns everything from begin to the end of the string, which correctly gives the last 3 characters here.

Exercise 15

Spec: Print every character in the string, one per line.

public static void printChars(String s) {
    for (int i = 0; i <= s.length(); i++) {
        System.out.println(s.charAt(i));
    }
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — charAt() is zero-indexed from the end of the string, not the beginning, so characters print in reverse order.
  • C. No — charAt() returns an int character code instead of a char, so this fails to compile.
  • D. No — the loop condition i <= s.length() allows i to equal s.length(), which is out of bounds for charAt() and throws a StringIndexOutOfBoundsException.
Answer

Answer: D — Valid indices for a string of length n are 0 through n - 1. The condition should be i < s.length().

Exercise 16

Spec: Print every character in the string, one per line.

public static void printChars(String s) {
    for (int i = 0; i < s.length(); i++) {
        System.out.println(s.charAt(i));
    }
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — this skips the last character because i < s.length() should be i <= s.length().
  • C. No — charAt() is deprecated and won't compile in modern Java.
  • D. No — the loop never terminates for empty strings because s.length() returns -1 for an empty string.
Answer

Answer: Ai < s.length() correctly visits indices 0 through length() - 1, printing every character exactly once. (length() returns 0 for an empty string, not -1, so the loop simply doesn't run.)

Exercise 17

Spec: Return true if the string contains the character 'z'. Example: hasZ("zoo")true.

public static boolean hasZ(String s) {
    return s.indexOf('z') >= 0;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — indexOf(char) only checks the first character of the string, not the whole string.
  • C. No — the comparison should be > 0, since a real match never legitimately returns 0.
  • D. No — indexOf(char) requires the argument to be a String like "z", not a char literal like 'z', so this fails to compile.
Answer

Answer: AindexOf() returns -1 when there's no match and a valid index (0 or greater) otherwise. Checking >= 0 correctly catches a match at any position, including a leading 'z' at index 0 — which is exactly why >= 0 is needed instead of > 0 (compare to Exercise 21, which makes this mistake).

Exercise 18

Spec: Return the extension of a filename (the text after the last .), or an empty string if there's no .. Example: "report.final.pdf""pdf".

public static String extension(String filename) {
    int dot = filename.lastIndexOf('.');
    if (dot == -1) {
        return "";
    }
    return filename.substring(dot + 1);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — lastIndexOf('.') finds the first dot, not the last, so "report.final.pdf" would incorrectly return "final.pdf".
  • C. No — substring(dot + 1) throws an exception when the filename ends in a dot.
  • D. No — this returns null instead of an empty string when there is no dot in the filename.
Answer

Answer: AlastIndexOf() correctly finds the last occurrence, and the -1 check correctly handles filenames with no extension.


Section 4: Searching & Comparing

Exercise 19

Spec: Return true if text contains word, ignoring case. Example: containsWord("Hello World", "world")true.

public static boolean containsWord(String text, String word) {
    return text.contains(word);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — contains() is case-sensitive, so "Hello World".contains("world") returns false. Both strings should be lower-cased first.
  • C. No — contains() can only check for single characters, not multi-character substrings.
  • D. No — contains() requires its argument to be a valid regular expression, and "world" isn't valid regex.
Answer

Answer: BString.contains() does an exact, case-sensitive substring search. To match the "ignoring case" requirement, both text and word need to be normalized first, e.g. text.toLowerCase().contains(word.toLowerCase()).

Exercise 20

Spec: Return true if text contains word, ignoring case.

public static boolean containsWord(String text, String word) {
    return text.toLowerCase().contains(word.toLowerCase());
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — toLowerCase() requires a Locale argument in modern Java, so this won't compile.
  • C. No — the two toLowerCase() calls must use the exact same Locale, or contains() will still fail to match.
  • D. No — contains() returns the index of the match rather than a boolean, so this doesn't compile.
Answer

Answer: A — Lower-casing both sides before comparing is the standard, correct way to do a case-insensitive contains() check.

Exercise 21

Spec: Return true if the URL uses HTTPS. Example: isSecure("https://example.com")true.

public static boolean isSecure(String url) {
    return url.equals("https://");
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — equals() only returns true when url is exactly "https://" with nothing after it. Real URLs like "https://example.com" incorrectly return false. This should use startsWith("https://").
  • C. No — String.equals() ignores everything after the first : character, so this only checks the protocol name.
  • D. No — this throws a NullPointerException whenever the URL doesn't start with "http".
Answer

Answer: B — The spec asks whether the URL starts with "https://", not whether it is "https://". equals() demands an exact full match, which almost no real URL will satisfy.

Exercise 22

Spec: Return true if the URL uses HTTPS.

public static boolean isSecure(String url) {
    return url.startsWith("https://");
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — startsWith() is case-insensitive, so it would also incorrectly match "HTTPS://" as a different protocol.
  • C. No — startsWith() checks only the first character, not the whole prefix, so "hxxps://" would also match.
  • D. No — startsWith() returns an int index instead of a boolean, so this fails to compile.
Answer

Answer: AstartsWith("https://") correctly checks the beginning of the string for that literal prefix. (startsWith() is actually case-sensitive, which is desirable here.)

Exercise 23

Spec: Return true if a comes before b alphabetically.

public static boolean comesBefore(String a, String b) {
    return a.compareTo(b) < 0;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — compareTo() already returns a boolean, so wrapping it in < 0 fails to compile.
  • C. No — compareTo() compares string lengths, not alphabetical order, so "ant" would only come before "ox" because it's shorter.
  • D. No — compareTo() is case-insensitive, so "Zebra" and "apple" would be compared only by length.
Answer

Answer: AcompareTo() performs a lexicographic (dictionary-order) comparison and returns a negative number when the calling string comes first. < 0 is the correct check for "comes before".

Exercise 24

Spec: Return true if text ends with the word "end".

public static boolean endsWithWord(String text) {
    return text.endsWith("end");
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — endsWith() also matches the substring anywhere inside the string, not just at the end, so "the end is near" would incorrectly return true.
  • C. No — endsWith() is case-insensitive, so "the END" would also match "end".
  • D. No — endsWith() returns the matching index as an int, not a boolean, so this fails to compile.
Answer

Answer: AendsWith() specifically checks the suffix of the string, unlike lastIndexOf()/indexOf(), which just find a match anywhere. "the end is near".endsWith("end") correctly returns false (it ends in "near"), while "weekend".endsWith("end") correctly returns true.


Section 5: Case, Whitespace & Trimming

Exercise 25

Spec: Return true if the string is empty or contains only whitespace.

public static boolean isBlank(String s) {
    return s.isEmpty();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — isEmpty() throws an exception when called on a string containing only whitespace.
  • C. No — isEmpty() only returns true for a zero-length string; a string like " " is not empty, so this misses whitespace-only input. It should use s.trim().isEmpty() (or s.isBlank() in Java 11+).
  • D. No — isEmpty() and s.length() == 0 give different results for the same string, so this method is unreliable.
Answer

Answer: CisEmpty() checks length, not whitespace content. A string of pure spaces has length > 0, so isEmpty() wrongly returns false for it.

Exercise 26

Spec: Return true if the string is empty or contains only whitespace.

public static boolean isBlank(String s) {
    return s.trim().isEmpty();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — trim() only removes leading whitespace, not trailing, so " hi " would still be considered blank.
  • C. No — trim() creates a new String, so isEmpty() checks the wrong object and always returns false.
  • D. No — this incorrectly reports strings like "cat" as blank, because trim() also strips ordinary letters that resemble whitespace.
Answer

Answer: Atrim() removes whitespace from both ends; if nothing but whitespace remains, .isEmpty() correctly returns true.

Exercise 27

Spec: Remove leading and trailing whitespace, including Unicode whitespace characters like the non-breaking space ( ).

public static String clean(String s) {
    return s.trim();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — trim() removes whitespace from the middle of the string too, changing content it shouldn't touch.
  • C. No — trim() was removed starting in Java 9, so this code will not compile.
  • D. No — trim() only removes characters with code point ≤ U+0020. It does not remove other Unicode whitespace like  . Use strip() (Java 11+), which follows proper Unicode whitespace rules.
Answer

Answer: Dtrim() predates Unicode-aware whitespace handling and only strips ASCII control/space characters at the ends. strip()/stripLeading()/stripTrailing() (Java 11+) use Character.isWhitespace(), which correctly recognizes non-breaking spaces and other Unicode whitespace.

Exercise 28

Spec: Remove leading and trailing whitespace, including Unicode whitespace characters. (Java 11+)

public static String clean(String s) {
    return s.strip();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — strip() requires a Locale argument to know which whitespace characters to remove.
  • C. No — strip() only removes leading whitespace; trailing whitespace needs a separate call to stripTrailing().
  • D. No — strip() is only available on StringBuilder, not String, so this fails to compile.
Answer

Answer: Astrip() removes Unicode-aware whitespace from both ends in a single call, correctly satisfying the spec.

Exercise 29

Spec: Capitalize the first letter of each word and lowercase the rest. Words are separated by single spaces. Example: "hello WORLD""Hello World".

public static String titleCase(String s) {
    String[] words = s.split(" ");
    StringBuilder result = new StringBuilder();
    for (String w : words) {
        result.append(Character.toUpperCase(w.charAt(0)));
        result.append(w.substring(1));
        result.append(" ");
    }
    return result.toString().trim();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — split(" ") throws an exception whenever there are two consecutive spaces in the string.
  • C. No — the code capitalizes the first letter but appends w.substring(1) unchanged, so it never lowercases the rest of the word; "WORLD" becomes "WORLD" instead of "World".
  • D. No — Character.toUpperCase() cannot be applied to a single char, only to full Strings.
Answer

Answer: C — Only the first character is forced to uppercase; the remainder of each word is appended verbatim, so any letters that were already uppercase (like the rest of "WORLD") stay uppercase instead of being lowercased.

Exercise 30

Spec: Capitalize the first letter of each word and lowercase the rest.

public static String titleCase(String s) {
    String[] words = s.split(" ");
    StringBuilder result = new StringBuilder();
    for (String w : words) {
        result.append(Character.toUpperCase(w.charAt(0)));
        result.append(w.substring(1).toLowerCase());
        result.append(" ");
    }
    return result.toString().trim();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — calling .trim() at the end also removes the spaces between words, collapsing "Hello World" into "HelloWorld".
  • C. No — w.substring(1).toLowerCase() throws an exception whenever a word is longer than 10 characters.
  • D. No — Character.toUpperCase() returns an int, not a char, so result.append(...) appends a number instead of a letter.
Answer

Answer: A — Each word's first letter is uppercased and the rest is lowercased before appending; the final .trim() only removes the single trailing space added after the last word, not the internal separators (those are literal single characters appended by the loop, not whitespace runs trim() would collapse).


Section 6: Splitting & Joining

Exercise 31

Spec: Split a version string like "1.2.3" into its three numeric parts.

public static String[] parts(String version) {
    return version.split(".");
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — split() can only split on a single literal character, and "." isn't a valid character to split on.
  • C. No — split() removes the delimiter but keeps only the first two resulting pieces, discarding the rest.
  • D. No — split() treats its argument as a regular expression, and an unescaped . matches any character. This splits between every character instead of at the literal dots; it should be split("\\.").
Answer

Answer: DString.split() takes a regex, and . in regex means "any character". "1.2.3".split(".") splits the string apart almost everywhere, not just at the actual dots.

Exercise 32

Spec: Split a version string like "1.2.3" into its three numeric parts.

public static String[] parts(String version) {
    return version.split("\\.");
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — \\. in a Java string literal represents two literal backslashes followed by a dot, so this still won't split correctly.
  • C. No — split() with a regex argument always returns a List<String>, not a String[], so this fails to compile.
  • D. No — this only works for version strings with exactly 3 parts; anything with 2 or 4 parts throws an exception.
Answer

Answer: A"\\." in Java source is the two-character regex \., which matches a literal dot. This correctly splits "1.2.3" into ["1", "2", "3"].

Exercise 33

Spec: Split a comma-separated string and return the number of fields, including trailing empty fields. Example: countFields("a,b,,")4.

public static int countFields(String csv) {
    return csv.split(",").length;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — split() throws an exception if the string ends with the delimiter character.
  • C. No — split() can only handle single-character delimiters when there are consecutive delimiters in a row.
  • D. No — split(",") uses a default limit of 0, which discards trailing empty strings. "a,b,,".split(",") returns ["a", "b"] (length 2), not 4. Use split(",", -1) to keep them.
Answer

Answer: D — By default, split() strips trailing empty strings from the result. To count them, pass a negative limit: csv.split(",", -1).

Exercise 34

Spec: Split a comma-separated string and return the number of fields, including trailing empty fields.

public static int countFields(String csv) {
    return csv.split(",", -1).length;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — passing -1 as the limit means "return at most negative one results," which is always zero.
  • C. No — the second argument to split() must be a regex pattern, not a number, so this fails to compile.
  • D. No — split(",", -1) removes empty strings from the beginning of the array but keeps trailing ones, so leading empty fields would still be miscounted.
Answer

Answer: A — A negative limit tells split() to apply the pattern as many times as possible and keep all resulting fields, including trailing empty ones.

Exercise 35

Spec: Join a list of words with a comma and a space between them. Example: ["a", "b", "c"]"a, b, c".

public static String joinWords(java.util.List<String> words) {
    return String.join(", ", words);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — String.join() puts the delimiter before the first element too, producing ", a, b, c".
  • C. No — String.join() only accepts a String[] array, not a List<String>, so this fails to compile.
  • D. No — String.join() trims each element automatically, which would remove intentional spaces inside words.
Answer

Answer: AString.join(delimiter, iterable) places the delimiter strictly between elements, which is exactly what's needed here. It has overloads for both varargs CharSequence... and any Iterable<? extends CharSequence> (including List<String>).

Exercise 36

Spec: Join a list of words with commas, with no trailing comma. Example: ["a", "b", "c"]"a,b,c".

public static String joinWords(java.util.List<String> words) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < words.size(); i++) {
        sb.append(words.get(i));
        if (i < words.size() - 1) {
            sb.append(",");
        }
    }
    return sb.toString();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — calling words.size() inside the loop condition recalculates the list size every iteration, which breaks for lists larger than 10 elements.
  • C. No — the condition i < words.size() - 1 skips appending the last word entirely, not just its trailing comma.
  • D. No — StringBuilder.append() cannot be called more than once per loop iteration, so this fails to compile.
Answer

Answer: A — The comma is only appended when there's a next element (i < words.size() - 1), so every word is appended and only the interior gaps get commas — no trailing comma. (String.join, from Exercise 35, is the simpler way to do this, but this manual version is also correct.)


Section 7: StringBuilder & Building Strings

Exercise 37

Spec: Reverse a string. Example: "hello""olleh".

public static String reverse(String s) {
    StringBuilder sb = new StringBuilder(s);
    sb.reverse();
    return s;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — StringBuilder.reverse() does not actually reverse character order; it only reverses the internal capacity array.
  • C. No — sb.reverse() mutates sb, but the method returns the original variable s, which is untouched. It should return sb.toString();.
  • D. No — reverse() only works correctly on strings with an even number of characters.
Answer

Answer: Csb.reverse() correctly reverses the characters inside sb, but the method then returns s — the original, unreversed String — instead of sb.toString().

Exercise 38

Spec: Reverse a string.

public static String reverse(String s) {
    return new StringBuilder(s).reverse().toString();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — chaining .reverse().toString() calls toString() before reverse() finishes, due to Java's method call ordering.
  • C. No — StringBuilder's constructor copies only the first 16 characters of s by default, truncating longer strings.
  • D. No — this reverses the words in a sentence, not the individual characters.
Answer

Answer: A — Method chaining executes left to right: the StringBuilder is built from s, then reversed in place, then converted back to a String. This correctly reverses the characters.

Exercise 39

Spec: Return true if the string is a palindrome (reads the same forwards and backwards), case-sensitive. Example: "racecar"true, "hello"false.

public static boolean isPalindrome(String s) {
    String reversed = new StringBuilder(s).reverse().toString();
    return s.equals(reversed);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — s.equals(reversed) always returns false because reversed is a different object created by StringBuilder, and equals() behaves like == in that case.
  • C. No — StringBuilder.reverse() reverses word order, not character order, so this checks whether the sentence reads the same in reverse word order.
  • D. No — palindrome checks must ignore case and punctuation by definition, so this implementation is too strict for any valid input.
Answer

Answer: AString.equals() always compares content, regardless of how the strings were constructed, so this correctly compares s to its reverse. (D is a common trap: the spec given here is explicitly case-sensitive with no punctuation handling — don't assume requirements beyond what's stated.)

Exercise 40

Spec: Return true if the string is a palindrome, ignoring case. Example: "Racecar"true.

public static boolean isPalindrome(String s) {
    String lower = s.toLowerCase();
    String reversed = new StringBuilder(lower).reverse().toString();
    return lower.equals(reversed);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — toLowerCase() must also be called on reversed, or the comparison fails because reversed retains the original mixed case.
  • C. No — this only works correctly for palindromes with an odd number of characters.
  • D. No — String.toLowerCase() throws an exception when the string contains digits or punctuation.
Answer

Answer: Alower is computed once, up front, and reversed is built from lower, so it's already all lowercase — no second toLowerCase() call is needed. "Racecar".toLowerCase()"racecar", whose reverse is also "racecar", so the comparison correctly succeeds.

Exercise 41

Spec: Remove the character at a given index from a string. Example: removeAt("hello", 1)"hllo".

public static String removeAt(String s, int index) {
    StringBuilder sb = new StringBuilder(s);
    sb.deleteCharAt(index);
    return sb.toString();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — deleteCharAt() removes the character after the given index, not at it, so removeAt("hello", 1) would return "helo".
  • C. No — deleteCharAt() shifts all later indices by two positions instead of one, corrupting the rest of the string.
  • D. No — StringBuilder's internal array is fixed-size, so deleteCharAt() throws an exception unless extra capacity was reserved.
Answer

Answer: AdeleteCharAt(index) removes exactly the character at that index and shifts the rest left by one. deleteCharAt(1) on "hello" removes 'e', giving "hllo", matching the spec.

Exercise 42

Spec: Count how many times substr appears in text, non-overlapping. Assume substr is non-empty. Example: countOccurrences("abababab", "ab")4.

public static int countOccurrences(String text, String substr) {
    int count = 0;
    int index = text.indexOf(substr);
    while (index != -1) {
        count++;
        index = text.indexOf(substr, index + substr.length());
    }
    return count;
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — index + substr.length() skips past valid overlapping matches, undercounting occurrences like "aa" inside "aaa".
  • C. No — text.indexOf(substr, ...) throws an exception once index reaches the end of the string.
  • D. No — this requires importing java.util.regex, because indexOf(String, int) is a regex-based method.
Answer

Answer: A — Each match advances the search past the matched text (index + substr.length()), so matches are counted non-overlapping, exactly as the spec asks. indexOf with a start position beyond the string simply returns -1 rather than throwing, so the loop terminates cleanly. (B correctly describes why overlapping matches aren't counted — but the spec explicitly asks for non-overlapping counting, so that's the intended behavior, not a bug.)


Section 8: Formatting, Parsing & Null-Safety

Exercise 43

Spec: Format a name and age into "Name is Age years old.". Example: describe("Ana", 30)"Ana is 30 years old.".

public static String describe(String name, int age) {
    return String.format("%d is %s years old.", name, age);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — format specifiers are matched positionally to arguments; %d is matched to name (a String), which throws an IllegalFormatConversionException at runtime. The specifiers should be "%s is %d years old.".
  • C. No — String.format() ignores mismatched specifiers and just prints arguments in the order given, regardless of %s or %d.
  • D. No — String.format() requires exactly as many % specifiers as there are periods in the format string.
Answer

Answer: B%d expects a numeric argument and is matched to the first argument, name. Since name is a String, this throws at runtime rather than producing output.

Exercise 44

Spec: Format a name and age into "Name is Age years old.".

public static String describe(String name, int age) {
    return String.format("%s is %d years old.", name, age);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — %d requires the argument to be a long, not an int, so this throws an exception.
  • C. No — String.format() returns void and prints directly to the console, so this method actually returns null.
  • D. No — the specifiers are applied in reverse order, so this prints the age first.
Answer

Answer: A%s correctly matches the String argument and %d correctly matches the int argument, in order, producing "Ana is 30 years old.".

Exercise 45

Spec: Format an integer with leading zeros to a width of 5. Example: padded(42)"00042".

public static String padded(int n) {
    return String.format("%5d", n);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — %5d pads with spaces to reach a width of 5, not zeros; padded(42) returns " 42", not "00042". Use %05d to zero-pad.
  • C. No — the 5 in %5d sets the number of decimal places, not the total width, so this only works for numbers under 5 digits.
  • D. No — String.format() cannot pad integers at all; padding only works with %s for strings.
Answer

Answer: B — A plain width specifier like %5d pads with spaces. The 0 flag (%05d) is what tells it to pad with zeros instead.

Exercise 46

Spec: Format an integer with leading zeros to a width of 5.

public static String padded(int n) {
    return String.format("%05d", n);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — %05d pads with zeros only on negative numbers; positive numbers are still padded with spaces.
  • C. No — this throws an exception whenever n is already 5 digits or longer.
  • D. No — the leading 0 in the specifier is interpreted as an argument index, not a padding flag, so this inserts the number twice.
Answer

Answer: A — The 0 flag in %05d zero-pads the number to the given width for any non-negative int. padded(42) correctly returns "00042".

Exercise 47

Spec: Return a string of n dashes. Example: repeatDash(4)"----". (Java 11+)

public static String repeatDash(int n) {
    return "-".repeat(n);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — repeat() concatenates the string with itself only once regardless of n, always returning "--" for any positive n.
  • C. No — repeat() is a StringBuilder-only method, not available on String, so this fails to compile.
  • D. No — repeat(n) repeats the string n + 1 times due to an off-by-one in its implementation.
Answer

Answer: AString.repeat(n) (Java 11+) returns the string concatenated with itself n times, so "-".repeat(4) correctly returns "----".

Exercise 48

Spec: Convert an object to its string representation, returning the text "null" if the object is null (rather than throwing).

public static String describe(Object obj) {
    return obj.toString();
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — toString() only works on Strings, not general Objects, so this fails to compile for non-String arguments.
  • C. No — calling .toString() on obj throws a NullPointerException when obj is null. Use String.valueOf(obj), which safely returns the text "null" instead of throwing.
  • D. No — toString() and String.valueOf() behave identically in every case, so this is just a differently-named equivalent.
Answer

Answer: Cobj.toString() requires dereferencing obj, which fails immediately if obj is null. String.valueOf(Object) explicitly checks for null first and returns the literal text "null" instead.

Exercise 49

Spec: Convert an object to its string representation, returning the text "null" if the object is null.

public static String describe(Object obj) {
    return String.valueOf(obj);
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — String.valueOf() throws a NullPointerException for null input, just like calling toString() directly.
  • C. No — String.valueOf(Object) requires an explicit cast to Object, or it resolves to the wrong overload for non-null arguments.
  • D. No — String.valueOf() returns an empty string "" for null input instead of the text "null".
Answer

Answer: AString.valueOf(Object obj) is documented to return "null" when obj is null, and obj.toString() otherwise. This correctly satisfies the spec without risking a NullPointerException.

Exercise 50

Spec: Remove all periods from a string. Example: "a.b.c""abc".

public static String removeDots(String s) {
    return s.replaceAll(".", "");
}

Does this code do this correctly? If not, why not?

  • A. Yes, it is correct.
  • B. No — replaceAll() only removes the first matching character, not all occurrences, despite the name.
  • C. No — replaceAll() cannot take an empty string as its replacement argument and throws an exception.
  • D. No — replaceAll() treats its first argument as a regular expression, and an unescaped . matches any character, so this removes every character in the string, returning "" instead of "abc". Use s.replace(".", "") (literal, see Exercise 10) or s.replaceAll("\\.", "") (escaped regex).
Answer

Answer: D — Just like split() (Exercise 31), replaceAll() takes a regex pattern. An unescaped . matches any character, so every character gets "replaced" with nothing, leaving an empty string.


Answer Key (quick reference)

# Ans # Ans # Ans # Ans
1 B 14 A 27 D 40 A
2 A 15 D 28 A 41 A
3 A 16 A 29 C 42 A
4 C 17 A 30 A 43 B
5 A 18 A 31 D 44 A
6 A 19 B 32 A 45 B
7 D 20 A 33 D 46 A
8 A 21 B 34 A 47 A
9 A 22 A 35 A 48 C
10 A 23 A 36 A 49 A
11 B 24 A 37 C 50 D
12 A 25 C 38 A
13 C 26 A 39 A