Skip to content
ExponaBLOG
JAVA, SPRING FRAMEWORK & SPRING BOOT · DAY 019 / 100

Java interview questions: strings and memory in practice

Classic questions that still show up — with answers that show depth.

Expona Admin7 min read

Java interview questions: strings and memory in practice

String handling is one of the most frequently asked topics in Java interviews, yet many developers treat it as a solved problem after their first year. Java interview questions: strings and memory in practice surface the depth of understanding that separates mid-level engineers from those ready for senior roles. We'll work through the four core questions that still show up in production code decisions today.

Why are strings immutable?

Immutability in Java strings is not an accident—it's a foundational design choice that affects security, performance, and thread safety. When a String object is created, its internal character array never changes. Once assigned, a string cannot be modified.

The first reason is security. Imagine you're using a string to hold a database password or API key. If strings were mutable, any code with a reference to that string could alter it mid-use, introducing security vulnerabilities. Password managers, encryption libraries, and authentication frameworks all depend on immutable strings to guarantee that sensitive data won't be unexpectedly changed.

The second reason is hash code caching. When you use a String as a key in a HashMap or HashSet, the hash code is computed once and cached. Because the String never changes, the hash code remains stable, and the string can be reliably found in the collection. If strings were mutable, modifying a string after it's been used as a map key would leave it orphaned in the wrong bucket.

The third reason is safe sharing via the string pool. Multiple references can safely point to the same String object because no code can modify it. The JVM doesn't need synchronization or defensive copying.

Here's what happens in practice:

java

String original = "production";
String reference = original;
reference = reference + "_backup";

// original is still "production"
// reference is now "production_backup"
// The original was never modified; a new String was created
System.out.println(original); // production
System.out.println(reference); // production_backup

The concatenation operation creates a new String object; it doesn't modify the existing one. This is the immutability contract in action.

== vs equals: references versus content

This is the question that separates those who've shipped code from those who've debugged it at 3 AM. The == operator compares object references, not content. The equals() method compares content and must be overridden for meaningful comparison.

With strings, this distinction matters because of the string pool. Two strings with the same content might or might not be the same object in memory.

java

String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");

System.out.println(s1 == s2);        // true (both point to same pool object)
System.out.println(s1 == s3);        // false (s3 is a new object)
System.out.println(s1.equals(s2));   // true
System.out.println(s1.equals(s3));   // true

In this example, s1 and s2 are both string literals, so the JVM puts them in the string pool and they reference the same object. s3 uses new String(), which explicitly creates a new object in the heap, separate from the pool. With ==, only s1 and s2 match. With equals(), all three match because the content is identical.

Always use equals() for string comparison in real code. The == operator relies on implementation details of the string pool, and as soon as a string is built from user input, concatenation, or external sources, you lose the guarantee that identical content means the same object reference.

java

String userInput = scanner.nextLine(); // "admin"
String hardcoded = "admin";

if (userInput == hardcoded) {  // Dangerous: might work, might not
  // ...
}

if (userInput.equals(hardcoded)) {  // Safe: always works
  // ...
}

StringBuilder vs concatenation in loops

The compiler is smarter than you think about simple string concatenation, but it has limits. Understanding when to use StringBuilder separates efficient code from code that silently degrades.

For a single concatenation, the compiler optimizes it:

java

String result = "Hello" + " " + "World";
// The compiler converts this to:
// String result = new StringBuilder().append("Hello").append(" ").append("World").toString();

It's efficient because only one StringBuilder is created. However, in a loop, the story changes:

java

// Without StringBuilder: creates N new StringBuilders
String result = "";
for (int i = 0; i < 10000; i++) {
  result = result + "item" + i;
  // Each concatenation creates a new String and a new StringBuilder
}

// With StringBuilder: creates 1 StringBuilder
StringBuilder result = new StringBuilder();
for (int i = 0; i < 10000; i++) {
  result.append("item").append(i);
}
String finalResult = result.toString();

Without StringBuilder in a loop, the compiler creates a new StringBuilder for each iteration. The string from the previous iteration must be copied into the new StringBuilder, then concatenated. This is O(n²) complexity—a performance cliff that bites production systems handling large datasets.

With StringBuilder, you append to the same mutable buffer each time. One allocation (well, with resizing), one object, linear time. In benchmarks with 10,000 iterations, the difference is often 100–1000x slower without StringBuilder.

Use StringBuilder for loops and chains of operations. The compiler handles simple concatenation in linear code.

What is the string pool?

The string pool is a special memory region managed by the JVM where string literals are stored and reused. It's an internal optimization to save heap memory when identical strings appear multiple times in your code.

When the JVM encounters a string literal during class loading, it checks if an identical string already exists in the pool. If it does, it reuses the reference. If not, it adds the new string to the pool.

java

String a = "database";
String b = "database";
String c = new String("database");

System.out.println(a == b);  // true: same pool entry
System.out.println(a == c);  // false: c is outside the pool
System.out.println(a.intern() == c.intern());  // true: intern() adds c to pool

The intern() method explicitly adds a string to the pool (or returns the existing pool entry). Calling intern() on a string forces it into the pool, so a.intern() == c.intern() is true.

In production, the string pool is most visible when you're working with configuration values, enum names, or repeated literals across modules. The pool saves memory because identical strings share one object. However, do not rely on the string pool for equality checks—it's an implementation detail. Use equals().

One cautionary note: strings created at runtime (from user input, file I/O, or computed values) do not automatically enter the pool. The pool is primarily for compile-time string literals. If you read the same value from a database 1,000 times, you'll have 1,000 separate String objects unless you explicitly intern them—and that's usually not worth the overhead.

Common mistakes

  • Using == for string equality checks. It works with string literals but fails with dynamically created strings. Always use equals() or equalsIgnoreCase().
  • Forgetting StringBuilder in loops. Simple concatenation in a loop creates O(n²) behavior and will throttle performance on large datasets.
  • Assuming all strings are in the pool. Only compile-time literals are automatically pooled. Runtime strings are not unless you call intern(), which is rarely necessary.
  • Calling intern() without measurement. Interning has overhead and can bloat the pool if applied indiscriminately. Benchmark before using it.
  • Forgetting that immutability applies to substrings. substring() returns a new String, not a view. If you're extracting many substrings, be aware of the memory impact.

Key takeaways

  • String immutability provides security, enables hash code caching, and allows safe object sharing without synchronization.
  • Use equals() for string content comparison; == compares references and is unreliable outside the string pool.
  • The compiler optimizes simple concatenation, but loops require StringBuilder to avoid quadratic performance degradation.
  • The string pool is a JVM optimization for compile-time literals; do not design your code around it or rely on it for equality.
  • Immutable strings make multithreaded code safer and collections like HashMap more reliable.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles