Common String Interview Questions
Why is concatenation in a loop inefficient with String?
Section titled “Why is concatenation in a loop inefficient with String?”String is immutable, so each concatenation creates a new String object, copying all characters from previous strings. In a loop, this results in O(n²) time complexity and excessive object creation.
Interview Example:
// INEFFICIENT: Creates n String objectsString result = "";for (int i = 0; i < 1000; i++) { result += "Hello"; // Creates new String each iteration}// Iteration 1: Creates "Hello" (5 chars copied)// Iteration 2: Creates "HelloHello" (10 chars copied)// Iteration 3: Creates "HelloHelloHello" (15 chars copied)// Total: 5 + 10 + 15 + ... = O(n²) character copies
// EFFICIENT: Uses single mutable StringBuilderStringBuilder sb = new StringBuilder();for (int i = 0; i < 1000; i++) { sb.append("Hello"); // Modifies same object}String result = sb.toString(); // Total: O(n) operations
// Performance comparisonlong start = System.nanoTime();String s = "";for (int i = 0; i < 10000; i++) { s += "a";}long stringTime = System.nanoTime() - start;System.out.println("String time: " + stringTime + " ns");start = System.nanoTime();StringBuilder builder = new StringBuilder();for (int i = 0; i < 10000; i++) { builder.append("a");}long sbTime = System.nanoTime() - start;System.out.println("StringBuilder time: " + sbTime + " ns"); // StringBuilder is 100-1000x fasterWhy it’s slow:
-
Each
+=creates new String object -
Copies all previous characters plus new ones
-
Leaves old objects for garbage collection
How does StringBuilder improve performance in loops?
Section titled “How does StringBuilder improve performance in loops?”StringBuilder is mutable and uses a resizable char array internally. It appends without creating new objects, achieving O(1) amortized time per append and O(n) total complexity.
Interview Example:
// StringBuilder internal mechanismpublic class StringBuilderDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); // Initial capacity: 16 for (int i = 0; i < 100; i++) { sb.append("X"); // O(1) amortized } // When capacity exceeded: array doubles (16 → 34 → 70 → 142) // Resize happens occasionally, not every time System.out.println(sb.toString()); }}
// Performance improvement visualizationpublic static void demonstratePerformance() { int iterations = 50000; // String: O(n²) complexity long start = System.currentTimeMillis(); String s = ""; for (int i = 0; i < iterations; i++) { s += "test"; } long stringTime = System.currentTimeMillis() - start; // StringBuilder: O(n) complexity start = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < iterations; i++) { sb.append("test"); } String result = sb.toString(); long sbTime = System.currentTimeMillis() - start; System.out.println("String: " + stringTime + " ms"); System.out.println("StringBuilder: " + sbTime + " ms"); System.out.println("Speedup: " + (stringTime / sbTime) + "x"); // Output: StringBuilder is 500-1000x faster}
// Pre-sizing for optimal performanceStringBuilder optimized = new StringBuilder(5000); // Pre-allocatefor (int i = 0; i < 1000; i++) { optimized.append("test");}// No array resizing needed, even fasterKey advantages:
-
Single mutable object modified in-place
-
Amortized O(1) append operation
-
Reduces garbage collection pressure
What is the time complexity of common string operations like substring() or concat()?
Section titled “What is the time complexity of common string operations like substring() or concat()?”Different operations have different complexities based on implementation. Modern Java optimizes some operations internally.
Interview Example:
String str = "Hello World"; // Length n// 1. length() - O(1)int len = str.length(); // Returns cached field value// 2. charAt(index) - O(1)char ch = str.charAt(5); // Direct array access// 3. substring(start, end) - O(n) where n = substring lengthString sub = str.substring(0, 5); // Creates new String, copies chars// In Java 7+: Creates new char array// In Java 6: Shared array with offset (memory leak risk)// 4. concat(String) - O(n + m)String result = str.concat(" Java"); // n + m chars copied// Internally: creates new char array of size (n + m)// 5. replace(old, new) - O(n)String replaced = str.replace('o', 'O'); // Scans entire string// 6. indexOf(String) - O(n * m) worst caseint index = str.indexOf("World"); // n = text, m = pattern// Average case: O(n)// 7. equals(String) - O(n)boolean equal = str.equals("Hello World"); // Compares each char// 8. toLowerCase() / toUpperCase() - O(n)String lower = str.toLowerCase(); // Creates new String, converts each char// 9. split(regex) - O(n)String[] parts = str.split(" "); // Scans string once// 10. trim() / strip() - O(n)String trimmed = " Hello ".trim(); // Scans from both ends// Summary table:// Operation | Time Complexity | Space Complexity// -----------------|-----------------|------------------// length() | O(1) | O(1)// charAt(i) | O(1) | O(1)// substring(i,j) | O(j-i) | O(j-i)// concat(s) | O(n+m) | O(n+m)// indexOf(s) | O(n*m) | O(1)// replace() | O(n) | O(n)// equals() | O(n) | O(1)// toLowerCase() | O(n) | O(n)Important: substring() in Java 7+ creates a new char array (safe), while Java 6 shared the array (faster but memory leak risk).
Why is + operator allowed with strings in Java? How does it work internally?
Section titled “Why is + operator allowed with strings in Java? How does it work internally?”The + operator is syntactic sugar for String concatenation, provided by Java language design. Compiler converts it to StringBuilder calls (Java 8) or invokedynamic (Java 9+).
Interview Example:
// Source code:String result = "Hello" + " " + "World" + 2025;// Java 8 bytecode (approx):String result = new StringBuilder() .append("Hello") .append(" ") .append("World") .append(2025) .toString();// Java 9+ bytecode (invokedynamic):// Uses invokedynamic instruction for optimization// Runtime decides best concatenation strategy// Compiler optimization for constantsString s1 = "Hello" + "World"; // Compiled to: String s1 = "HelloWorld"; (compile-time constant)String s2 = "Hello" + " " + "World"; // Compiled to: String s2 = "Hello World";// But with variables, no compile-time optimizationString a = "Hello";String b = "World";String s3 = a + b; // Uses StringBuilder at runtime// Why + is allowed:// 1. Convenience and readabilityString greeting = "Hello " + name + ", welcome!";// 2. Overloaded for String only (not user-definable)// Java doesn't allow operator overloading except for String +// 3. Automatic type conversionString mix = "Count: " + 42 + ", Flag: " + true; // Converts int and boolean to String automatically// Under the hood (conceptual):public static String plus(String a, String b) { return new StringBuilder().append(a).append(b).toString();}Key Points:
-
Only built-in operator overloading in Java
-
Compiler magic, not available for custom classes
-
Optimized differently based on Java version
How does the compiler optimize string concatenation?
Section titled “How does the compiler optimize string concatenation?”The compiler performs compile-time constant folding for literals and uses StringBuilder or invokedynamic for runtime concatenation.
Interview Example:
// 1. Compile-time constant foldingString s1 = "Hello" + " " + "World"; // Compiler optimizes to: String s1 = "Hello World"; // Single literal in bytecodefinal String prefix = "Hello";String s2 = prefix + " World"; // Compiler can optimize because prefix is final constant// Result: "Hello World" (compile-time)// 2. Runtime StringBuilder optimizationString name = "Java";String s3 = "Hello " + name + " World"; // Java 8: Converted to StringBuilder// String s3 = new StringBuilder("Hello ").append(name).append(" World").toString();// Java 9+: Uses invokedynamic// More flexible, runtime chooses best strategy:// - MH_INLINE_SIZED_EXACT (default): Pre-calculates exact size// - BC_SB_SIZED: Uses StringBuilder with size estimation// - Others based on -Djava.lang.invoke.stringConcat parameter// 3. Single statement optimizationString result = a + b + c + d; // Optimized to single StringBuilder:// String result = new StringBuilder().append(a).append(b).append(c).append(d).toString();// 4. NO optimization across statementsString result = "";result = result + "a"; // Creates new StringBuilderresult = result + "b"; // Creates another StringBuilderresult = result + "c"; // Creates yet another StringBuilder// Each line creates new StringBuilder! (inefficient)// 5. Loop concatenation NOT optimizedfor (int i = 0; i < 100; i++) { result += "X"; // New StringBuilder each iteration (BAD)}// Must manually use StringBuilder for loops// Example: Compiler optimization levelspublic static void demonstrateOptimization() { // Optimized (compile-time): String opt1 = "A" + "B" + "C"; // Optimized (single expression): String s = "Hello"; String opt2 = "Prefix " + s + " Suffix"; // NOT optimized (separate statements): String notOpt = "Start"; notOpt = notOpt + " Middle"; // New object notOpt = notOpt + " End"; // New object // NOT optimized (loop): String loop = ""; for (int i = 0; i < 10; i++) { loop += i; // Very inefficient }}Java 9+ invokedynamic optimization:
-
Calculates exact required size upfront
-
Uses package-private String constructor (fastest)
-
Avoids intermediate String objects
What happens when you concatenate null with a string?
Section titled “What happens when you concatenate null with a string?”null is converted to the string “null” (not NullPointerException). This is handled by String.valueOf() internally.
Interview Example:
// null concatenation behaviorString s1 = null;String result1 = "Hello " + s1;System.out.println(result1); // "Hello null"// Equivalent to:String result1b = "Hello " + String.valueOf(s1); // String.valueOf(null) returns "null"// More examplesString s2 = null;String result2 = s2 + " World";System.out.println(result2); // "null World"Object obj = null;String result3 = "Object: " + obj;System.out.println(result3); // "Object: null"// Both nullString a = null;String b = null;String result4 = a + b;System.out.println(result4); // "nullnull"// With StringBuilderStringBuilder sb = new StringBuilder();sb.append(null);System.out.println(sb.toString()); // "null"// String.valueOf() implementation (conceptual):public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString();}// Practical example - avoiding "null" in outputString name = null;String message = "Hello " + (name != null ? name : "Guest");System.out.println(message); // "Hello Guest"// Or use Objects.toString() (Java 7+)String safe = "User: " + Objects.toString(name, "Anonymous");System.out.println(safe); // "User: Anonymous"// CAUTION: Direct method calls on null throw exceptionString s = null;// int len = s.length(); // NullPointerException// But concatenation works:String concat = "Value: " + s; // "Value: null" (OK)Key Point: Concatenation converts null to “null” string, but method calls on null throw NullPointerException.
How to safely compare strings to avoid NullPointerException?
Section titled “How to safely compare strings to avoid NullPointerException?”Use constant-first comparison, Objects.equals(), or null checks before comparison.
Interview Example:
// 1. Constant-first comparison (Yoda conditions)String userInput = getUserInput(); // May return null// SAFE: Constant on leftif ("admin".equals(userInput)) { System.out.println("Admin user");}// UNSAFE: May throw NullPointerException// if (userInput.equals("admin")) { // NPE if userInput is null }// 2. Using Objects.equals() (Java 7+)String s1 = null;String s2 = "test";System.out.println(Objects.equals(s1, s2)); // false (null-safe)System.out.println(Objects.equals(s1, null)); // trueSystem.out.println(Objects.equals(s2, s2)); // true// 3. Explicit null checkString username = getUsername();if (username != null && username.equals("admin")) { System.out.println("Admin access");}// 4. Using Optional (Java 8+)Optional<String> optName = Optional.ofNullable(getName());optName.filter(name -> name.equals("admin")) .ifPresent(name -> System.out.println("Admin"));// 5. Case-insensitive safe comparisonString input = getUserInput();if ("ADMIN".equalsIgnoreCase(input)) { // Safe even if input is null System.out.println("Admin");}// 6. Multiple null-safe comparisonsString a = null;String b = "test";String c = null;// Traditionalif ((a == null && b == null) || (a != null && a.equals(b))) { // ...}// Using Objects.equals()if (Objects.equals(a, b)) { // Much cleaner // ...}// 7. String comparison best practicespublic static boolean safeEquals(String s1, String s2) { if (s1 == s2) return true; // Same reference or both null if (s1 == null || s2 == null) return false; return s1.equals(s2);}// Test casesSystem.out.println(safeEquals(null, null)); // trueSystem.out.println(safeEquals("test", null)); // falseSystem.out.println(safeEquals(null, "test")); // falseSystem.out.println(safeEquals("test", "test")); // true// 8. Using Apache Commons Lang (if available)// StringUtils.equals(s1, s2); // Null-safeBest Practices:
-
Constant first:
"constant".equals(variable) -
Objects.equals(): For null-safe comparison
-
Null check first:
if (s != null && s.equals(...))
What’s the difference between String.format(), concatenation, and StringBuilder?
Section titled “What’s the difference between String.format(), concatenation, and StringBuilder?”Each has different use cases: String.format() for templates, concatenation for simple cases, StringBuilder for loops and complex building.
Interview Example:
String name = "Java";int version = 21;double price = 99.99;// 1. String.format() - Template-based, readableString formatted = String.format("Language: %s, Version: %d, Price: $%.2f", name, version, price);System.out.println(formatted); // Output: "Language: Java, Version: 21, Price: $99.99"// Pros: Readable, localization support, formatting control// Cons: Slower, overhead of parsing format string// 2. + Concatenation - Simple and quickString concat = "Language: " + name + ", Version: " + version + ", Price: $" + price;// Pros: Simple, compiler-optimized for single expressions// Cons: Inefficient in loops, less control over formatting// 3. StringBuilder - Manual but efficientStringBuilder sb = new StringBuilder();sb.append("Language: ").append(name) .append(", Version: ").append(version) .append(", Price: $").append(price);String built = sb.toString();// Pros: Fast, efficient for loops, no intermediate objects// Cons: More verbose, manual formatting// Performance comparisonpublic static void comparePerformance() { int iterations = 10000; // Test 1: String.format() long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { String s = String.format("Number: %d", i); } long formatTime = System.nanoTime() - start; // Test 2: Concatenation start = System.nanoTime(); for (int i = 0; i < iterations; i++) { String s = "Number: " + i; } long concatTime = System.nanoTime() - start; // Test 3: StringBuilder start = System.nanoTime(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < iterations; i++) { sb.setLength(0); // Clear sb.append("Number: ").append(i); String s = sb.toString(); } long sbTime = System.nanoTime() - start; System.out.println("String.format(): " + formatTime + " ns"); System.out.println("Concatenation: " + concatTime + " ns"); System.out.println("StringBuilder: " + sbTime + " ns"); // Typical results: StringBuilder < Concat < String.format()}// Use cases:// Use String.format() for:String log = String.format("[%s] %s: %s", timestamp, level, message);String table = String.format("%-10s | %5d | %8.2f", name, count, amount);// Use + concatenation for:String simple = "Hello " + name;String error = "Error: " + errorCode + " - " + errorMsg;// Use StringBuilder for:StringBuilder html = new StringBuilder();html.append("<ul>");for (String item : items) { html.append("<li>").append(item).append("</li>");}html.append("</ul>");// Comparison table:// Method | Speed | Readability | Use Case// -----------------|----------|-------------|-------------------------// String.format() | Slowest | Best | Templates, formatting// + Concatenation | Medium | Good | Simple cases, few ops// StringBuilder | Fastest | Fair | Loops, complex building// Advanced: String.format() with argumentsString.format("Name: %s, Age: %d, Score: %.2f%%", "Alice", 25, 95.5); // Output: "Name: Alice, Age: 25, Score: 95.50%"// StringBuilder with capacityStringBuilder optimized = new StringBuilder(1000); // Pre-allocate to avoid resizing