Strings look deceptively simple — you have used them since your first
System.out.println("Hello"). But String is also the class behind the most repeated Java
interview questions ever asked: why is String immutable, what is the string pool, why does
== sometimes work and sometimes fail? Understanding what actually happens in memory turns
all of those from memorized trivia into answers you can derive on the spot.
A String is not a primitive like int from the
data types table — it is an object: internally a sequence
of characters wrapped in a class packed with useful methods.
Creating strings: literal vs new
There are two ways to create a string, and they behave differently in memory:
String a = "codebegun"; // literal — goes to the string pool
String b = "codebegun"; // reuses the SAME pooled object
String c = new String("codebegun"); // new object on the heap, outside the pool
System.out.println(a == b); // true — same object
System.out.println(a == c); // false — different objects
System.out.println(a.equals(c)); // true — same characters
With a literal, the JVM first checks the string pool — a cache of unique string objects
inside the heap. "codebegun" already exists when b is assigned, so b simply points to
the same object as a. With new, Java is forced to create a distinct object regardless
of the pool, which is why a == c is false.
This is why new String("...") is considered a code smell: it defeats the pool's memory
sharing for no benefit. Where exactly the pool and heap live is covered in
Java memory management.
Common mistake: A common mistake beginners make is comparing strings with
==. It appears to work in small test programs because literals share pooled objects — then fails in real code the moment a string arrives from user input, a file or a database, because those strings are not pooled. Content comparison is alwaysequals(), orequalsIgnoreCase()when case does not matter.
Immutability: why strings never change
Every String is immutable: after creation, its characters cannot be altered. Methods
that appear to modify a string actually return a brand-new one:
public class ImmutableDemo {
public static void main(String[] args) {
String name = "java";
name.toUpperCase(); // result thrown away!
System.out.println(name); // java — unchanged
name = name.toUpperCase(); // reassign to capture the new string
System.out.println(name); // JAVA
}
}
The first toUpperCase() call did produce "JAVA" — but as a new object that was
immediately discarded because nothing captured it. Forgetting to reassign is one of the
top five bugs in fresher code.
Why did Java's designers choose immutability?
- Pool safety. Dozens of variables may share one pooled object. If strings were mutable, changing one variable's text would silently change all of them.
- Hash caching. A string's hash code is computed once and cached, making
Stringthe ideal key type for a HashMap. Mutable keys would corrupt the map. - Thread safety. Immutable objects can be shared across threads with no locking.
- Security. File paths, URLs and class names are validated as strings; immutability guarantees they cannot change between validation and use.
The methods you will actually use
String has 70+ methods; these fifteen or so cover 95% of real work:
| Method | Example | Result |
|---|---|---|
length() |
"hello".length() |
5 |
charAt(i) |
"hello".charAt(1) |
'e' |
substring(b, e) |
"hello".substring(1, 4) |
"ell" |
indexOf(s) |
"hello".indexOf("ll") |
2 |
contains(s) |
"hello".contains("ell") |
true |
replace(a, b) |
"hello".replace('l', 'p') |
"heppo" |
trim() / strip() |
" hi ".strip() |
"hi" |
split(regex) |
"a,b,c".split(",") |
["a", "b", "c"] |
toUpperCase() |
"hi".toUpperCase() |
"HI" |
startsWith(s) |
"hello".startsWith("he") |
true |
isEmpty() / isBlank() |
" ".isBlank() |
true |
Two details worth flagging. substring(begin, end) includes begin but excludes end —
so the length of the result is always end - begin. And split returns a
String array, which is how you break a CSV line into fields.
Here they are working together in a small, runnable validator:
public class EmailChecker {
public static void main(String[] args) {
String input = " Ravi.Kumar@Example.COM ";
String email = input.strip().toLowerCase();
boolean valid = email.contains("@")
&& email.indexOf("@") > 0
&& email.endsWith(".com");
String username = email.substring(0, email.indexOf("@"));
System.out.println("Email: " + email); // ravi.kumar@example.com
System.out.println("Valid: " + valid); // true
System.out.println("User: " + username); // ravi.kumar
}
}
Notice the chaining in input.strip().toLowerCase(): each call returns a new string that
the next call operates on. Chaining works precisely because every method returns a
String.
Comparing strings properly
Three comparison tools, three different jobs:
equals(other)— character-by-character equality, case-sensitive.equalsIgnoreCase(other)— equality ignoring case; ideal for emails and usernames.compareTo(other)— lexicographic ordering; returns negative, zero or positive, which is what sorting uses.
Strings also work in switch statements (Java 7+), matching by equals() — see
control statements for the syntax. One guard to
remember: call equals on the value you know is not null, or use
"admin".equals(role) style so a null role returns false instead of throwing.
Interview note: "How many objects does
String s = new String("java");create?" The expected answer: up to two — the pooled"java"literal, plus the heap object thatnewforces. Follow-up:s.intern()returns the pooled version. If you can draw the pool and heap on the whiteboard while explaining, this question is finished in a minute.
StringBuilder: when immutability becomes expensive
Immutability has a cost: every concatenation creates a new object. Inside a loop, that cost explodes:
public class ConcatBenchmark {
public static void main(String[] args) {
int n = 50_000;
long t1 = System.currentTimeMillis();
String report = "";
for (int i = 0; i < n; i++) {
report += i + ","; // new String object EVERY pass
}
long stringMs = System.currentTimeMillis() - t1;
long t2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(i).append(','); // modifies one internal buffer
}
String report2 = sb.toString();
long builderMs = System.currentTimeMillis() - t2;
System.out.println("String += : " + stringMs + " ms");
System.out.println("StringBuilder : " + builderMs + " ms");
}
}
Run it: the += loop typically takes seconds while StringBuilder finishes in
milliseconds, because += copies the entire accumulated string on every iteration —
roughly O(n²) total work — while StringBuilder appends into a resizable internal buffer.
The rule of thumb: + is fine for joining a handful of values in one expression (the
compiler optimizes that case anyway). The moment concatenation happens inside a loop,
switch to StringBuilder.
StringBuffer is the legacy, synchronized twin of StringBuilder — same API, slower due
to locking. Mention it in interviews as "thread-safe but rarely needed"; write
StringBuilder in code.
Pro tip:
StringBuilderalso gives you the easiest string-reverse in Java:new StringBuilder(s).reverse().toString(). Interviewers may ask you to reverse without it — using achararray and two indexes — so practise both versions.
Formatting and joining: the underrated helpers
Three static helpers save you from clumsy concatenation in everyday code. String.format
builds a string from a template: String.format("%s scored %d (%.1f%%)", name, marks, pct) reads far better than five + operations, and the same format codes work in
System.out.printf. String.join glues a collection or array with a separator —
String.join(", ", cities) produces "Hyderabad, Pune, Chennai" in one line, replacing
the loop-plus-trailing-comma dance entirely. And String.valueOf(x) converts any value,
including null, into a string without throwing.
Going the other direction — string to number — is the job of the wrapper classes:
Integer.parseInt("42") and Double.parseDouble("3.14"). Both throw
NumberFormatException on bad input such as "42abc" or an empty string, so any parsing
of user input belongs inside a try-catch. Freshers lose easy marks in machine tests by
letting unvalidated parseInt calls crash on the tester's edge-case input; a two-line
catch block is usually the difference.
Strings and memory: a quick map
Pulling the pieces together: all String objects live on the heap. The string pool is a
region inside the heap holding one canonical copy of each literal. Variables hold
references. == compares those references; equals() follows them and compares contents.
Because pooled objects are shared and strings are immutable, none of this sharing can ever
produce surprising changes — that is the whole design, and once you can narrate it, most
string interview questions answer themselves.
Practice before you move on
Solid drills, each doable with what this page covered: count vowels in a sentence, check whether a string is a palindrome, find the first non-repeating character, reverse the words in a sentence (not the letters), and split a CSV line and total its numeric fields. String manipulation appears in almost every fresher machine test we have seen companies run, which is why the Java Full Stack course at CodeBegun dedicates a full practice block to strings before moving into collections.
Frequently Asked Questions
Why are strings immutable in Java?
What is the string pool in Java?
What is the difference between == and equals() for strings?
What is the difference between String, StringBuilder and StringBuffer?
How many objects does new String("java") create?
Can we use strings in a switch statement?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

