Skip to content
beginnerPhase 10 · Java Arrays & Strings

String Immutability

Understand why Strings are immutable and how the String pool works.

30m
2 problems
Topic Progress0%

Immutability

Why Strings Are Immutable

Java Strings are immutable by design, meaning once a String object is created, its value cannot be changed.

What Immutability Means

String str = "Hello";

// This does NOT modify str
String upper = str.toUpperCase();  // "HELLO"

// str is still "Hello"
// A new String object is created for the result
System.out.println(str);      // Hello
System.out.println(upper);    // HELLO

Reasons for Immutability

// 1. Security
// Strings are used for:
// - Class loading (Class.forName("com.example.MyClass"))
// - Network connections (URL)
// - File paths
// - Database connection strings
// If mutable, these could be changed maliciously

// 2. Thread Safety
// Immutable objects are inherently thread-safe
// Multiple threads can read without synchronization
String shared = "constant";  // Safe to share

// 3. Hash Code Caching
// Can be computed once and cached
String str = "Hello";
int hash1 = str.hashCode();  // Computed
int hash2 = str.hashCode();  // Returns cached value

// 4. String Pool Enablement
// Can safely share identical strings
String a = "Hello";
String b = "Hello";
// Both point to same object in pool

Memory Model

// String literal
String a = "Hello";
// Created in string pool, shared

// new String()
String b = new String("Hello");
// Created in heap, separate object

// intern()
String c = b.intern();
// Added to pool, returns pool reference

Immutability vs const

// Java doesn't have 'const' keyword like C++
// Use 'final' for variables, not values
final String name = "Alice";
// name = "Bob";  // COMPILE ERROR

// But String methods return new objects
String str = "Hello";
str.toUpperCase();  // Returns new String, str unchanged
// Must reassign: str = str.toUpperCase();

String Pool

String Pool Mechanism

The string pool is a special memory area where string literals are stored to save memory.

How Pool Works

// Literal creation
String a = "Hello";  // Created in pool
String b = "Hello";  // Reuses pool object

System.out.println(a == b);  // true (same object)
System.out.println(a.equals(b));  // true (same content)

// new String() bypasses pool
String c = new String("Hello");  // Created in heap
System.out.println(a == c);  // false (different objects)
System.out.println(a.equals(c));  // true (same content)

intern() Method

// Manually add to pool
String a = new String("Hello");  // Not in pool
String b = a.intern();  // Added to pool
String c = "Hello";     // Already in pool

System.out.println(b == c);  // true

// Useful when:
// 1. Reading strings from external sources
// 2. Many duplicate strings
// 3. Memory optimization

Pool Behavior by Java Version

// Java 6: PermGen space (fixed size, limited)
// Can cause: java.lang.OutOfMemoryError: PermGen space

// Java 7+: Heap memory (garbage collected)
// More flexible, no PermGen issues

// Modern JVMs: G1GC with string deduplication
// Can deduplicate strings even with different references

Pool Pitfalls

// Pitfall 1: String concatenation in pool
String a = "Hello";
String b = " World";
String c = a + b;  // NOT in pool!
// c is created in heap

// Pitfall 2: new String() with pool string
String d = new String("Hello");
// d is not in pool, even though "Hello" is

// Pitfall 3: intern() in loops
for (int i = 0; i < 1000000; i++) {
    String s = new String("hello").intern();
    // This adds to pool, but pool has limited size
}

Best Practices

// 1. Use literals when possible
String good = "Hello";  // In pool
String bad = new String("Hello");  // Not in pool

// 2. Use equals() for comparison
if (str.equals("constant")) { }

// 3. Avoid intern() unless necessary
// It's a micro-optimization in most cases

// 4. Be careful with string concatenation
// Use StringBuilder for multiple concatenations

Interning

String Interning

Interning is the process of adding a string to the pool and returning its reference.

How intern() Works

String a = new String("Hello");
String b = a.intern();

// a: Not in pool (heap object)
// b: In pool (pool reference)

String c = "Hello";  // In pool

System.out.println(b == c);  // true
System.out.println(a == c);  // false

When to Use intern()

// 1. Reading strings from files/database
String dbString = readFromDatabase();
String interned = dbString.intern();

// 2. Many duplicate strings
// Without intern(): 1000 objects for 1000 "hello" strings
// With intern(): 1 object for 1000 "hello" strings

// 3. String comparison optimization
// If many comparisons, intern first
String[] largeArray = readLargeFile();
for (String s : largeArray) {
    if (s.intern() == "target") {
        // Found!
    }
}

Performance Considerations

// intern() has O(1) lookup but:
// - Pool has limited size
// - Can cause GC pressure
// - Not always faster than equals()

// Benchmark example:
String a = new String("Hello");
String b = "Hello";

// Method 1: equals()
for (int i = 0; i < 1000000; i++) {
    a.equals(b);
}

// Method 2: intern() + ==
String aInterned = a.intern();
for (int i = 0; i < 1000000; i++) {
    aInterned == b;
}
// Method 2 may be faster, but uses pool memory

Alternative: String Deduplication

// Java 9+ with G1GC
// Can deduplicate strings automatically
// Enable with: -XX:+UseG1GC -XX:+UseStringDeduplication

// This deduplicates:
// - Strings created by substring()
// - Strings from external sources
// - Any identical strings in heap

// No need to call intern()

Common Mistakes

// Mistake 1: intern() in tight loop
for (int i = 0; i < 1000000; i++) {
    String s = new String("hello").intern();
    // Adds to pool repeatedly, wastes memory
}

// Mistake 2: Assuming all strings are interned
String a = new String("Hello");
String b = "Hello";
if (a == b) { }  // FALSE!

// Mistake 3: Using intern() for equality
if (str.intern() == "constant") { }  // Works, but not recommended
if (str.equals("constant")) { }      // Better

Performance

Performance Implications

String Concatenation

// BAD: O(n^2) time complexity
String result = "";
for (int i = 0; i < 10000; i++) {
    result += i;  // Creates new String each iteration
}
// Creates 10000 String objects

// GOOD: O(n) time complexity
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String result = sb.toString();
// Creates 1 StringBuilder object

When to Use StringBuilder

// Use StringBuilder when:
// 1. Multiple concatenations in loop
StringBuilder sb = new StringBuilder();
for (String s : list) {
    sb.append(s).append(",");
}

// 2. Building strings incrementally
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");

// 3. String reversal
String reversed = new StringBuilder(str).reverse().toString();

// Use String when:
// 1. Simple concatenation
String s = "Hello" + " " + "World";  // Compiler optimizes

// 2. Few concatenations
String s = firstName + " " + lastName;

String Comparison Performance

// == is O(1) - reference comparison
// equals() is O(n) - character comparison

// If you intern strings:
String a = str.intern();
String b = target.intern();
if (a == b) { }  // O(1) comparison

// But intern() itself is O(n)
// So only beneficial if comparing same strings many times

Memory Usage

// String object overhead:
// - 4 bytes: hash code
// - 8 bytes: reference to char array
// - 16 bytes: object header
// - char array: 2 bytes per character

// Example: "Hello" (5 chars)
// Total: ~30 bytes per String object

// With 10000 "Hello" strings:
// Without pooling: 10000 * 30 = 300KB
// With pooling: 1 * 30 = 30 bytes

String vs StringBuilder

Scenario String StringBuilder
Single concatenation
Loop concatenation
Thread safety ✓ (immutable)
Mutable
Performance (loops) O(n²) O(n)

Best Practices

// 1. Use String for simple cases
String name = firstName + " " + lastName;

// 2. Use StringBuilder for loops
StringBuilder sb = new StringBuilder();
for (String s : list) {
    sb.append(s);
}

// 3. Pre-allocate StringBuilder capacity
StringBuilder sb = new StringBuilder(expectedSize);

// 4. Use String.format() for formatted strings
String msg = String.format("Name: %s, Age: %d", name, age);

Practice Problems

0/2solved
Implement strStr()
String Matching

Return the index of the first occurrence of needle in haystack, or -1 if not found.

Example:

Input: haystack = "hello", needle = "ll"

Output: 2

The substring 'll' starts at index 2

Optimal Solution — O(n*m) time, O(1) space

Brute force substring comparison.

public int strStr(String haystack, String needle) {
    if (needle.isEmpty()) return 0;
    for (int i = 0; i <= haystack.length() - needle.length(); i++) {
        if (haystack.substring(i, i + needle.length()).equals(needle)) {
            return i;
        }
    }
    return -1;
}

Edge Cases:

  • Empty needle
  • Needle longer than haystack
  • No match
String to Integer (atoi)
Parsing

Implement atoi to convert a string to a 32-bit signed integer.

Example:

Input: s = "42"

Output: 42

Convert string to integer

Optimal Solution — O(n) time, O(1) space

Parse character by character, handle edge cases.

public int myAtoi(String s) {
    int i = 0, sign = 1, result = 0;
    while (i < s.length() && s.charAt(i) == ' ') i++;
    if (i < s.length() && (s.charAt(i) == '+' || s.charAt(i) == '-')) {
        sign = s.charAt(i++) == '-' ? -1 : 1;
    }
    while (i < s.length() && s.charAt(i) >= '0' && s.charAt(i) <= '9') {
        if (result > Integer.MAX_VALUE / 10 || 
            (result == Integer.MAX_VALUE / 10 && s.charAt(i) - '0' > 7)) {
            return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
        }
        result = result * 10 + (s.charAt(i++) - '0');
    }
    return result * sign;
}

Edge Cases:

  • Leading zeros
  • Overflow
  • No digits

Quiz

1. What does it mean for a String to be immutable?

Question 1 options

2. What is the purpose of String.intern()?

Question 2 options

3. Why does string concatenation in loops create many objects?

Question 3 options

4. What is the primary purpose of String Immutability?

Question 4 options

Flashcards

Question

Why are Java Strings immutable?

Answer

For security (class loading, network), thread safety, hash code caching, and string pool support.

Question

What is the string pool?

Answer

A special memory area where string literals are stored to save memory by reusing identical strings.

Question

When should you use StringBuilder instead of String?

Answer

When doing multiple string concatenations in loops. StringBuilder is mutable and O(n) vs String O(n²).

Question

What is String Immutability?

Answer

String Immutability is a key concept in Java programming.

Question

When to use String Immutability?

Answer

Use String Immutability when building production systems that require reliability, scalability, and maintainability.

Revision Notes

Key Takeaways

  • 1.Strings are immutable for security, thread safety, and performance
  • 2.String literals are stored in string pool and reused
  • 3.Use equals() for content comparison, never ==
  • 4.Use StringBuilder for multiple concatenations in loops

Interview Tips

  • Explain why immutability is important for security
  • Know the difference between == and equals()
  • Understand when to use StringBuilder vs String
  • Remember string pool behavior with literals vs new String()

Cheat Sheet

Cheat Sheet

  • Immutable: String value cannot change after creation
  • Pool: String literals stored in string pool
  • intern(): Manually add string to pool
  • ==: Reference comparison (use for pool strings)
  • equals(): Content comparison (always use for strings)
  • StringBuilder: Use for multiple concatenations