“yes” is not “yes”?
Riya's quiz app asks “Ready to start?”. Her code says if (answer == "yes"). When she tests with answer = "yes"; in the code, it works. When a friend actually types yes, the app replies “OK, maybe later.”
Sir, both say “yes”. How can they not be equal?
== doesn't look at the letters. For objects, it asks: “are these the same object in memory?” Your typed “yes” is a new object; the “yes” in your code lives in a shared area called the String pool.
Then why did it work when I wrote answer = "yes" in the code?
Because both literals came from the pool, so they really were the same object. That's what makes this bug sneaky: it passes your tests and fails for real users. Use equals() to compare text, always.
What are the String pool, == and equals()?
The String pool (string constant pool) is a JVM-managed table of unique String objects. String literals and compile-time constant expressions are stored there once and shared. For references, == tests reference equality (same object), while equals(), overridden in String, tests content equality (same sequence of characters).
== asks “same house?”. equals() asks “same people living inside?”. Two houses can have identical families. For text, you almost always want the second question.
Words you'll meet in this lesson
a == b: both variables point to the same object.a.equals(b): same characters in the same order."Ja" + "va".compareTo uses.Which comparison should I use?
| Same text, exact | a.equals(b) |
|---|---|
| Same text, ignore case | a.equalsIgnoreCase(b) |
| One side may be null | "yes".equals(answer) or Objects.equals(a, b) |
| Order (sorting) | a.compareTo(b): negative, 0 or positive; compareToIgnoreCase ignores case |
| Same object? | a == b (rarely what you want for Strings) |
| Primitives | == is correct for int, char, boolean … |
Exam tip: String s1 = "Java"; String s2 = new String("Java"); → s1 == s2 is false, s1.equals(s2) is true. Also: new String("Java") can create two objects: the pool literal (if not already there) and the new one on the heap.
When are two Strings the same object?
| How the String was made | Where it lives | == "Java" |
|---|---|---|
"Java" (literal) | Pool, shared | true |
"Ja" + "va" (constants) | Pool: javac joins it at compile time | true |
final String p = "Ja"; p + "va" | Pool: p is a constant too | true |
p + q (normal variables) | New object at run time | false |
new String("Java") | New object, always | false |
scanner.nextLine(), file, network, database | New objects at run time | false |
s.intern() | Returns the pool copy | true |
The pattern: only values known at compile time are pooled automatically. Everything a real user, file or database gives you is a new object, so == fails exactly where it matters.
Proof, with a real compiler
public class Main {
public static void main(String[] args) {
String a = "Java";
String p = "Ja", q = "va";
final String fp = "Ja";
System.out.println(("Ja" + "va") == a);
System.out.println((p + q) == a);
System.out.println((fp + "va") == a);
System.out.println((p + q).equals(a));
}
}compareTo: dictionary order
Negative
"apple".compareTo("banana") → negative: apple comes first.
Zero
"Java".compareTo("Java") → 0: same text.
Positive
"b".compareTo("a") → positive: b comes after a.
Case matters: compareTo compares character codes, and all capital letters come before all small letters. So “Zara” sorts before “aman”. Use compareToIgnoreCase for name lists.
What the JVM does with Strings
Class loads: literals go to the pool
The first time the literal "Java" is used, the JVM puts one String object in the pool. Every later use of the same literal, anywhere in the program, gets that same object. SoString a = "Java"; String b = "Java";a == bis true.new always makes a new object
String c = new String("Java");newcreates a separate heap object with the same characters.a == cis false. There's almost never a reason to write this.Run-time values are new objects
Input, file contents, results ofString typed = sc.nextLine(); // user types Java+on variables,substring,toUpperCase: all build new String objects at run time, outside the pool.equals compares characters
if (typed.equals(a)) { … }String.equalsfirst checks if it's the same object (fast yes), then the lengths, then each character. Different objects with equal text → true.Make it null-safe
Calling a method on a literal can never throwif ("Java".equals(typed)) { … }NullPointerException, andequals(null)just returns false.
Pool, heap, == and equals(): both views
Left: the code and its result. Right: four variables and where their Strings really are.
- Literal:
arefers to "Java". - Same literal:
a == bis true. Both are the very same object. - new:
chas the same text, buta == cis false. - equals: compares characters, so
a.equals(c)is true. - Real input: the typed "Java" is a new object.
==fails,equalsworks. This is the bug from the story. - intern(): returns the pooled "Java", so the comparison with
ais true.
- String literals are kept once in the pool, which lives on the heap (since Java 7).
- Both variables hold the same address, so
==, which compares addresses for objects, is true. new String("Java")copies the characters into a fresh object; its address is different.String.equalscompares lengths and then characters one by one. The addresses don't matter.- Scanner builds the String from the bytes you typed, at run time. It's never automatically pooled.
intern()finds the equal String in the pool (adding one if missing) and returns it. Useful in rare memory-saving cases, not for comparisons.
String comparison in real systems
Text comparison is everywhere: logins, coupons, roles, search, sorting. A wrong comparison is a real bug that reaches users.
Watch it with an example: sorting student names
compareTo compares two Strings character by character using their Unicode numbers. Watch why “arjun” lands after “Bela”, and how compareToIgnoreCase fixes it.
- Sorting:
Arrays.sorton Strings usescompareTo. Each character has a number (its Unicode code): 'a' is 97, 'B' is 66. - First difference: the very first characters already differ. compareTo returns 97 − 66 = 31.
- Positive means “after”: so "arjun" is placed after "Bela". Users see a list that looks wrong: all capitalised names first.
- Fix: compare ignoring case. Now 'a' is compared with 'b': 97 − 98 = −1, negative, so arjun comes first.
- Result: Aman, arjun, Bela: the order a person expects. Contact lists and student rosters sort like this.
1. Login and verification
OTPs, usernames and security answers typed by users are compared with stored values using equals (or a constant-time compare for secrets), after normalising case and spaces where appropriate.
2. Coupons and promo codes
Codes are matched case-insensitively against the active list, so “fest50” works. Status values like "ACTIVE" or "EXPIRED" from the database are compared with constants.
3. Sorting lists
Contact lists, product names and dropdown options are sorted with compareTo or a comparator, usually ignoring case, or using a locale-aware Collator for languages with accents.
4. Memory saving by sharing
The pool lets every class share one copy of each literal. Large systems that load millions of repeated values (city names, status codes) can deduplicate them too; the JVM's G1 collector even has an option for it.
Examples are simplified. Passwords themselves are never compared as plain text; they're hashed and the hashes are compared.
Four Strings, one word
public class Main {
public static void main(String[] args) {
String a = "Java";
String b = "Java";
String c = new String("Java");
System.out.println("a == b : " + (a == b));
System.out.println("a == c : " + (a == c));
System.out.println("a.equals(c) : " + a.equals(c));
System.out.println("a == c.intern(): " + (a == c.intern()));
System.out.println("\"java\".equalsIgnoreCase(a): " + "java".equalsIgnoreCase(a));
}
}(a == b)Brackets matter: without them,"a == b : " + awould be joined first and then compared.new String("Java")Forces a separate object, used here only to demonstrate.a.equals(c)The right way to compare text."java".equalsIgnoreCase(a)Ignores case; calling it on the literal is also null-safe.
Follow the addresses
Objects are numbered #1, #2 so you can see which variable points where.
Worked examples: real problems, solved and traced in memory
Three production-style problems where comparing Strings the wrong way breaks the business rule. Run each one, then step through it and compare the object numbers (#1, #2 …): same number means same object, which is all == checks.
Role from the database: == vs equals
Problem statement
A user's role is loaded from the database as text. The admin page must open only for "ADMIN". Compare the role with == and with equals and see which one gives the right answer.
role from DB = "ADMIN" (created at run time)
== says: false equals says: true
Approach
- Values read from a database, file or keyboard are new String objects, not the pooled literal.
==compares the two references (object numbers).equalscompares the characters, which is what the business rule means.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Coupon code in any case
Problem statement
A customer types a coupon as " fest50 ". The active coupons are FEST50 and NEW10. A method findCoupon must return the matching code's index, ignoring spaces and case, or -1.
typed = " fest50 ", active = {"FEST50", "NEW10"}Matched: FEST50
Approach
- Normalise the input once with
trim()(a new String). - Loop over the active codes and compare with
equalsIgnoreCase. - Return the index as soon as one matches; return -1 if the loop finishes.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Free delivery check that survives null
Problem statement
Orders to Pune get free delivery. Some customers never entered a city, so their city is null. Write deliveryFee(city) so it never crashes: 0 for Pune, 40 otherwise.
cities = {"Pune", null, "Delhi"}Pune -> Rs 0 null -> Rs 40 Delhi -> Rs 40
Approach
- Calling a method on
nullthrows NullPointerException, so never writecity.equals("Pune"). - Call
equalson the literal instead:"Pune".equals(city)simply returns false for null. - Use a ternary to choose the fee.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 4.4 in one screen
- Literals and compile-time constants are stored once in the String pool and shared.
new String(…), input, files, databases and run-time joins create new objects.==→ same object?equals→ same characters?- Compare text with
equals/equalsIgnoreCase, never==. - Null-safe:
"literal".equals(x)orObjects.equals(a, b). compareTo: negative / 0 / positive; capitals sort before small letters.intern()returns the pooled copy; rarely needed.
Interview and viva questions
What is the difference between == and equals() for Strings?
== compares references: true only if both variables point to the same object. equals() is overridden in String to compare characters. Two different String objects with the same text are equals but not ==.
How many objects does String s = new String("Hello") create?
Up to two: the literal "Hello" in the pool (if it isn't there already from earlier code) and one new String object on the heap that s refers to.
What is the String pool and why does it exist?
A JVM table of unique String objects for literals and interned Strings. Because Strings are immutable, they can be safely shared, saving memory when the same text appears many times in a program.
What does intern() do?
It returns the canonical pooled String with the same content: if one exists it's returned, otherwise this String is added to the pool. It can save memory for many repeated values, but should not be used as a way to make == comparisons work.
Why can the same == code give different results in testing and production?
In tests, values are often literals in the code, which are pooled, so == happens to be true. In production the values come from users, files or databases as new objects, so == is false. That's why == on Strings is a bug even when tests pass.
Practice problems
Every problem here compares Strings, so every answer uses equals, equalsIgnoreCase or compareTo, never ==. The last three are real business tasks.
Yes or no, any case
A user answers a question. Accept “yes”, “YES”, “Yes” and so on, even with spaces around it.
answers = " Yes", "YES", "yep", "no"
[ Yes] -> agreed [YES] -> agreed [yep] -> not agreed [no] -> not agreed
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] answers = {" Yes", "YES", "yep", "no"};
for (String a : answers) {
boolean agreed = a.trim().equalsIgnoreCase("yes");
System.out.println("[" + a + "] -> " + (agreed ? "agreed" : "not agreed"));
}
}
}How it works: trim() removes outer spaces (returning a new String), then equalsIgnoreCase compares letters without caring about case. == would fail even for an exact match typed by the user, because input creates new String objects.
Count a word in a list
Count how many orders in a list are for “Masala Dosa”.
orders = {"Idli", "Masala Dosa", "Vada", "Masala Dosa", "Upma"}Masala Dosa ordered 2 times
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] orders = {"Idli", "Masala Dosa", "Vada", "Masala Dosa", "Upma"};
String item = "Masala Dosa";
int count = 0;
for (String o : orders) {
if (item.equals(o)) count++;
}
System.out.println(item + " ordered " + count + " times");
}
}How it works: equals compares the characters. Writing the known value first, item.equals(o), is also safe if the array contains null.
Null-safe comparison
Some customers have no coupon (null). Check who used “FEST50” without crashing.
coupons = {"FEST50", null, "NEW10", "FEST50"}Customer 1 used FEST50 Customer 4 used FEST50
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] coupons = {"FEST50", null, "NEW10", "FEST50"};
for (int i = 0; i < coupons.length; i++) {
if ("FEST50".equals(coupons[i])) {
System.out.println("Customer " + (i + 1) + " used FEST50");
}
}
}
}How it works: "FEST50".equals(null) simply returns false. Writing coupons[i].equals("FEST50") would throw NullPointerException for customer 2. Objects.equals(a, b) is another null-safe option.
Alphabetical order of three names
Print three names in dictionary order using compareTo (without sorting methods).
a = "Meera", b = "Arjun", c = "Kabir"
Arjun, Kabir, Meera
Show solution and explanation
public class Main {
public static void main(String[] args) {
String a = "Meera", b = "Arjun", c = "Kabir", t;
if (a.compareTo(b) > 0) { t = a; a = b; b = t; }
if (b.compareTo(c) > 0) { t = b; b = c; c = t; }
if (a.compareTo(b) > 0) { t = a; a = b; b = t; }
System.out.println(a + ", " + b + ", " + c);
}
}How it works: x.compareTo(y) is negative if x comes first, 0 if equal, positive if x comes after. Three compare-and-swap steps sort three values. Note that compareTo is case-sensitive: all capitals come before all small letters.
Find duplicate names
Print every name that appears more than once in a list, once each.
names = {"Riya", "Kabir", "riya", "Meera", "Kabir", "Kabir"}Riya x2 Kabir x3
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] names = {"Riya", "Kabir", "riya", "Meera", "Kabir", "Kabir"};
for (int i = 0; i < names.length; i++) {
boolean seenBefore = false;
for (int k = 0; k < i; k++) {
if (names[k].equalsIgnoreCase(names[i])) { seenBefore = true; break; }
}
if (seenBefore) continue;
int count = 1;
for (int j = i + 1; j < names.length; j++) {
if (names[j].equalsIgnoreCase(names[i])) count++;
}
if (count > 1) System.out.println(names[i] + " x" + count);
}
}
}How it works: for each name we first check whether it appeared earlier (to report it only once), then count later matches. equalsIgnoreCase treats “Riya” and “riya” as the same person. This is O(n²); for large lists a HashMap is far faster (Module 11).
First and last in dictionary order
From a list of city names, find the one that comes first and the one that comes last alphabetically, ignoring case.
cities = {"pune", "Agra", "Mumbai", "delhi", "Kochi"}First: Agra, Last: pune
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] cities = {"pune", "Agra", "Mumbai", "delhi", "Kochi"};
String first = cities[0], last = cities[0];
for (String c : cities) {
if (c.compareToIgnoreCase(first) < 0) first = c;
if (c.compareToIgnoreCase(last) > 0) last = c;
}
System.out.println("First: " + first + ", Last: " + last);
}
}How it works: the same max/min pattern as for numbers (Lesson 4.1), but with compareToIgnoreCase instead of < and >. With plain compareTo, “Mumbai” would come before “delhi” because capital letters sort first.
Coupon code checker
Customers type coupon codes in any case, sometimes with spaces. Check each typed code against the list of active coupons and print the discount.
active = FEST50 (50%), NEW10 (10%), FREESHIP (0%); typed = " fest50 ", "NEW10", "Fest-50"
[ fest50 ] -> FEST50, 50% off [NEW10] -> NEW10, 10% off [Fest-50] -> invalid code
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] codes = {"FEST50", "NEW10", "FREESHIP"};
int[] percent = {50, 10, 0};
String[] typed = {" fest50 ", "NEW10", "Fest-50"};
for (String t : typed) {
String clean = t.trim();
int found = -1;
for (int i = 0; i < codes.length; i++) {
if (codes[i].equalsIgnoreCase(clean)) { found = i; break; }
}
if (found >= 0) {
System.out.println("[" + t + "] -> " + codes[found] + ", " + percent[found] + "% off");
} else {
System.out.println("[" + t + "] -> invalid code");
}
}
}
}How it works: parallel arrays hold each code and its discount at the same index. Normalising user input (trim, ignore case) before comparing avoids rejecting real customers for harmless typing differences, while “Fest-50” is still correctly rejected.
Role-based menu
A user's role arrives from the login system as text. Show the right menu for ADMIN, MANAGER and STAFF, and deny anything else, including a missing role.
roles = "ADMIN", "manager", null, "Guest"
ADMIN -> All settings, users, reports manager -> Reports, approvals null -> Access denied Guest -> Access denied
Show solution and explanation
public class Main {
static final String ADMIN = "ADMIN", MANAGER = "MANAGER", STAFF = "STAFF";
public static void main(String[] args) {
String[] roles = {"ADMIN", "manager", null, "Guest"};
for (String role : roles) {
String menu;
if (ADMIN.equalsIgnoreCase(role)) menu = "All settings, users, reports";
else if (MANAGER.equalsIgnoreCase(role)) menu = "Reports, approvals";
else if (STAFF.equalsIgnoreCase(role)) menu = "My tasks";
else menu = "Access denied";
System.out.println(role + " -> " + menu);
}
}
}How it works: constants are written once and compared with CONSTANT.equalsIgnoreCase(role), which is both case-tolerant and null-safe. The final else denies by default: unknown or missing roles get nothing, the safe choice for access control.
Remove duplicate sign-ups
A newsletter list has the same email typed in different cases and with spaces. Print a cleaned list with each email once, in lower case.
emails = {"Riya@Mail.com", " kabir@mail.com", "riya@mail.com ", "MEERA@mail.com", "Kabir@Mail.com"}3 unique subscribers: riya@mail.com kabir@mail.com meera@mail.com
Show solution and explanation
public class Main {
public static void main(String[] args) {
String[] emails = {"Riya@Mail.com", " kabir@mail.com", "riya@mail.com ", "MEERA@mail.com", "Kabir@Mail.com"};
String[] unique = new String[emails.length];
int count = 0;
for (String e : emails) {
String clean = e.trim().toLowerCase();
boolean exists = false;
for (int i = 0; i < count; i++) {
if (unique[i].equals(clean)) { exists = true; break; }
}
if (!exists) unique[count++] = clean;
}
System.out.println(count + " unique subscribers:");
for (int i = 0; i < count; i++) System.out.println(" " + unique[i]);
}
}How it works: normalise first (trim + lower case), then compare with equals. count tracks how much of the unique array is filled (Lesson 4.1). Sending duplicate emails annoys customers and can get a company's mail marked as spam.
Real-world scenario questions (asked in company interviews)
Comparison bugs often pass tests and fail in production. Decide your answer first, then open the model answer.
Admins can't see the admin page, but only in production
The check if (user.getRole() == "ADMIN") works in local tests, where the test sets the role with the literal "ADMIN". In production, roles are loaded from the database and no one can open the admin page. Explain.
The interviewer is checking: do you understand pooling and why == hides the bug in tests?
Show a strong answer
- In the tests both sides are literals, so they're the same pooled object and
==is true by luck. - From the database, the driver builds a new String at run time: same text, different object, so
==is false. - Fix:
"ADMIN".equals(user.getRole())(null-safe), or better, store roles as an enum. - Make tests realistic: build values with
new String(…)or load them like production does; static analysis tools also flag==on Strings.
Avoid: fixing it with intern(). Comparing content is the correct intent.
Checkout crashes for customers without a city
Free delivery logic has if (customer.getCity().equals("Pune")). Customers who signed up with only a phone number have no city, and their checkout page crashes. What happened and how do you fix it?
The interviewer is checking: do you write null-safe comparisons?
Show a strong answer
getCity()returnsnull, and callingequalson null throwsNullPointerException.- Flip it:
"Pune".equals(customer.getCity()), or useObjects.equals(customer.getCity(), "Pune"). - Decide the business rule for missing data explicitly (e.g. ask for the city at checkout) rather than letting it crash.
Avoid: wrapping it in a try-catch that hides the problem.
“Why is Zara above aman?”
The contact list is sorted with a.compareTo(b). Users complain that all names starting with capitals appear before names typed in small letters, and names with accents (like “Élodie”) land at the very end. What would you change?
The interviewer is checking: do you know compareTo is code-point order, not human alphabetical order?
Show a strong answer
compareTocompares character codes: 'Z' (90) is less than 'a' (97), and accented letters have even higher codes.- Use
compareToIgnoreCaseorString.CASE_INSENSITIVE_ORDERfor case. - For proper language-aware ordering, use a
java.text.Collatorfor the user's locale. - Optionally store a normalised “sort name” in the database so sorting is done once, consistently.
Avoid: converting names to upper case permanently just to sort them.
Millions of copies of the same city name
A service loads 50 lakh customer records from CSV files into memory. A heap analysis shows that Strings like "Mumbai", "Bengaluru" and "ACTIVE" exist as millions of separate objects, using gigabytes. How could you reduce this?
The interviewer is checking: do you understand pooling beyond literals, and its trade-offs?
Show a strong answer
- Each value parsed from the file is a new String object, even if the text repeats.
- Deduplicate low-variety fields:
intern()them, or keep your ownHashMap<String, String>cache so equal values share one object. - Better still, model fixed values as enums (status) or IDs (city codes).
- The G1 garbage collector also offers automatic String deduplication (
-XX:+UseStringDeduplication), which shares the character arrays of equal Strings. - Measure before and after with a heap dump; don't intern high-variety values like emails, which adds cost without savings.
Avoid: “Just add more RAM” without understanding where memory goes.