Strings in Java: How They Actually Work in Memory

⏱ calculating…
Table of Contents

    Here's a question that trips up even developers with a few years of experience: if you concatenate strings inside a loop 10,000 times, why does your code suddenly get slow — even though each individual + looks like a tiny, cheap operation? The answer is the single most important thing to understand about strings in Java, and it comes down to one word: immutability.

    We covered Arrays last time as contiguous memory you can freely change. Strings look similar on the surface — they're basically a sequence of characters — but Java treats them very differently under the hood, and that difference is exactly what today's post is about.

    A String is Secretly a char Array

    At the lowest level, a Java String is backed by a character array (technically a byte array since Java 9's "compact strings," but the concept is the same). So why not just use char[] directly? Because String adds one critical guarantee on top: once created, its contents can never change.

    Why Strings Are Immutable

    When you write code like this:

    String greeting = "Hello";
    greeting = greeting + " World";
    

    It looks like you modified greeting in place. You didn't. Java created a brand new String object containing "Hello World" and pointed the greeting variable at it. The original "Hello" object still exists in memory — untouched — until the garbage collector eventually cleans it up.

    📝 Note

    Immutability isn't a limitation — it's a deliberate design choice. It makes strings safe to share across multiple parts of a program (and multiple threads) without one part accidentally corrupting another's data. It's also what makes the String Pool (next section) possible at all.

    The String Pool: Java's Memory-Saving Trick

    Because strings can't change, Java can safely reuse identical string literals instead of creating duplicates. This shared storage area is called the String Pool:

    String a = "hello";
    String b = "hello";
    System.out.println(a == b); // true — both point to the SAME pooled object
    
    String c = new String("hello");
    System.out.println(a == c); // false — new String() forces a new object,
                                 // bypassing the pool entirely
    System.out.println(a.equals(c)); // true — same characters, compared properly
    
    ⚠️ Warning

    This is one of the most common interview traps in Java: == compares whether two variables point to the exact same object in memory. .equals() compares whether the actual characters match. For strings, you almost always want .equals() — using == can pass in quick tests and then silently fail in production the moment a string doesn't come from a literal.

    Why String Concatenation in a Loop Gets Slow

    Now the loop problem from the intro makes sense. Every time you do result = result + something, Java throws away the old String object and builds an entirely new one, copying all the old characters plus the new ones. Do that inside a loop, and each iteration copies a little more than the last:

    // SLOW — O(n²). Each concatenation copies the entire string built so far.
    String result = "";
    for (int i = 0; i < 10000; i++) {
        result = result + i; // creates a brand new String object every time
    }
    

    The fix is StringBuilder — a mutable character buffer designed exactly for this situation:

    // FAST — O(n). StringBuilder grows in place, no repeated copying.
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10000; i++) {
        sb.append(i);
    }
    String result = sb.toString(); // convert to String only once, at the end
    
    💡 Tip

    Rule of thumb: a handful of concatenations outside a loop? Plain String is fine and more readable. Building a string inside a loop — especially one that could run many times? StringBuilder, every time.

    Common String Operations and Their Real Complexity

    OperationTime ComplexityWhy
    charAt(i)O(1)Direct index into the backing array
    length()O(1)Cached when the String is created
    substring()O(n)Copies the relevant characters into a new String
    concat() / +O(n)Creates a new String, copying both originals
    equals()O(n)Must compare characters until a mismatch or the end
    StringBuilder.append()O(1) amortizedGrows an internal buffer in place

    A Classic String Problem: Checking a Palindrome

    Let's put this together in code you'll genuinely see in interviews — checking whether a string reads the same forwards and backwards:

    public class PalindromeCheck {
        public static boolean isPalindrome(String s) {
            int left = 0;
            int right = s.length() - 1;
    
            while (left < right) {
                if (s.charAt(left) != s.charAt(right)) {
                    return false; // mismatch found — not a palindrome
                }
                left++;
                right--;
            }
            return true;
        }
    
        public static void main(String[] args) {
            System.out.println(isPalindrome("racecar")); // true
            System.out.println(isPalindrome("hello"));    // false
        }
    }
    

    Notice the technique: two pointers moving toward each other from opposite ends. It's O(n) time and O(1) extra space — no new string is built, just two index variables doing all the work. This "two-pointer" pattern shows up constantly once you get into searching and sorting, which is exactly where we're headed next.

    Common Interview Questions

    • Why are strings immutable in Java, and what problems would mutable strings cause?
    • What's the difference between == and .equals() for strings, and when would relying on == actually break?
    • Why is building a string inside a loop with + considered bad practice? What's the fix?
    • How would you reverse a string without using a built-in reverse method?

    The Takeaway

    Strings behave like arrays in some ways and completely differently in others — the immutability is the whole story, and it explains everything else: why == misleads people, why the String Pool exists, and why StringBuilder is a tool you'll reach for constantly. With Arrays and Strings both covered, you now have the two building blocks that almost every algorithm in this series operates on. Next up: Linear Search — the simplest possible way to find something, and the baseline every faster search algorithm after it will be measured against.

    Happy learning!
    — Team CodeElevateX 🚀

    Comments

    Popular posts from this blog

    Bubble Sort in Java — Explained Simply with Code

    What is Data Structures and Algorithms (DSA)? A Complete Beginner's Guide with Java

    Time and Space Complexity Explained with Java Examples | Complete Beginner's Guide