skip to content

Strings

Everything about String: immutability and the literal pool, comparison, building strings efficiently, the common API, text blocks, and compact-string storage. Strings are the single most-asked non-collection Java class in interviews.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

What do indexOf and lastIndexOf do in Java, and how do their fromIndex variants and return values behave?

level: juniorimportance: must knowfreq 70%

answer

  1. indexOf = first/leftmost; lastIndexOf = last/rightmost
  2. not found → -1 (basis of contains())
  3. indexOf(x, from) searches forward ≥ from; lastIndexOf(x, from) searches backward ≤ from
  4. empty "" matches: indexOf→0/clamped, lastIndexOf→length
  5. case-sensitive, literal — NOT regex

basics

~10 s

indexOf finds the first position of a character or substring and returns that index, or -1 if not found. lastIndexOf does the same but finds the last (rightmost) occurrence.

solid answer

~40 s

indexOf(target) returns the zero-based index of the first occurrence of a char or substring; lastIndexOf(target) returns the index of the last occurrence. Both return -1 when the target is absent — that sentinel is how you test 'contains', and indeed contains() is implemented via indexOf. The fromIndex overloads control where the search starts: indexOf(target, fromIndex) searches forward from fromIndex, while lastIndexOf(target, fromIndex) searches backward, treating fromIndex as the highest position allowed to start a match. For an empty target string, indexOf returns fromIndex (clamped to length) and lastIndexOf returns fromIndex too. The char overloads actually take an int code point, and search is case-sensitive and exact — there's no regex here, unlike split or matches.

go deeper

for a junior

Knows indexOf finds the first occurrence, lastIndexOf the last, and that -1 means not found.

for a middle

Correctly uses both fromIndex overloads, knows the search is case-sensitive and literal, and uses -1 (not 0) to test absence.

for a senior

Explains the opposite meaning of fromIndex in the two methods, the empty-string and clamping edge cases, the int/code-point overload, and that it's not regex.

for a principal

Advises on performance (naive scan complexity, when to switch to Pattern/automaton) and on code-point-correct searching for international text across a codebase.

## Purpose `indexOf` and `lastIndexOf` answer 'where does this character or substring appear?' They return a **zero-based index** (position counting from 0) or **-1** if the target does not occur. This -1 'sentinel' (a special return value signalling 'not found') is the idiomatic way to test presence; `String.contains(cs)` is literally `indexOf(cs.toString()) > -1`. ## The four core forms For both a single character and a substring: - `indexOf(target)` — first (leftmost) occurrence, scanning left→right. - `indexOf(target, fromIndex)` — first occurrence **at or after** `fromIndex`. - `lastIndexOf(target)` — last (rightmost) occurrence, scanning right→left. - `lastIndexOf(target, fromIndex)` — last occurrence that **starts at or before** `fromIndex`. Example with `"banana"` (b=0,a=1,n=2,a=3,n=4,a=5): - `"banana".indexOf('a')` → 1 - `"banana".lastIndexOf('a')` → 5 - `"banana".indexOf('a', 2)` → 3 (first 'a' at index ≥ 2) - `"banana".lastIndexOf('a', 4)` → 3 (last 'a' at index ≤ 4) - `"banana".indexOf("na")` → 2 (substring match returns the start index) - `"banana".indexOf('z')` → -1 (not present) ## fromIndex direction is the tricky part The same parameter name means opposite things: - For `indexOf`, `fromIndex` is the **lower bound** — the search moves **forward** and won't return an index below it. A `fromIndex` below 0 is treated as 0; above `length` yields -1 (or, for an empty target, the clamped length). - For `lastIndexOf`, `fromIndex` is the **upper bound** on where a match may **start** — the search moves **backward** from there. A `fromIndex` above `length` is clamped down; below 0 yields -1. ## The empty-string special case Searching for the empty string `""` always 'matches' at a position. `"abc".indexOf("")` returns 0; `"abc".indexOf("", 2)` returns 2; `"abc".indexOf("", 99)` returns 3 (clamped to length). `"abc".lastIndexOf("")` returns the length, 3. This falls out of the matching definition and occasionally surprises people. ## char overloads take an int The character overloads are declared as `indexOf(int ch)`, accepting a **code point** (the numeric Unicode value), so they can match supplementary characters above the BMP that don't fit in a single `char`. The substring overloads match exact char sequences. All matching is **case-sensitive** and **literal** — these methods do **not** interpret regular expressions, unlike `split`, `matches`, `replaceAll`. To find ignoring case, lowercase both sides first or use a regex method. ## Complexity These run in O(n·m) worst case (naive scan of length-n text for length-m needle) — fine for typical inputs; for heavy repeated searching consider a precompiled `Pattern` or specialized algorithm.

  • How is String.contains implemented, and why does that matter?
    contains(cs) returns indexOf(cs.toString()) > -1. It matters because contains is just a convenience over indexOf, so it is also case-sensitive and literal (no regex), and shares indexOf's performance.
  • What does "abc".indexOf("", 99) return and why?
    It returns 3. The empty string matches everywhere, and fromIndex 99 is clamped to the string length 3, so the reported match position is 3.

saying these in an interview costs you the question

  • Thinking lastIndexOf's fromIndex is a lower bound like indexOf's
  • Expecting these to interpret regex patterns
  • Returning a default of 0 instead of treating -1 as 'not found'
  • Assuming the search is case-insensitive
  • Confusing the returned start index of a substring match with a length

context

open as a page

How does String.substring() work in Java, including its index arguments and what happens to the original string?

level: juniorimportance: must knowfreq 78%

basics

~10 s

substring(begin, end) returns the part of the string from begin up to but not including end. The original string is unchanged because strings are immutable; you get a new string back.

open as a page

Why do we use StringBuilder or StringBuffer to build strings instead of repeatedly concatenating with the + operator in a loop?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Java Strings are immutable, so each + creates a brand-new String object. In a loop that copies the text over and over, which is slow and wasteful. StringBuilder edits one mutable buffer in place, so it is much faster.

open as a page

How does the + operator work for joining strings in Java, and what does it produce?

level: juniorimportance: must knowfreq 80%

basics

~10 s

The + operator joins two strings into a new string. Because String objects can't be changed, every + makes a brand-new String holding the combined text; the originals stay untouched.

open as a page

In Java, what is the difference between using == and equals() to compare two String objects?

level: juniorimportance: must knowfreq 90%

basics

~10 s

== checks whether two String variables point to the same object in memory. equals() checks whether the two strings contain the same characters. To compare text, always use equals().

open as a page

What does it mean that String is immutable in Java, and why was it designed that way?

level: juniorimportance: must knowfreq 85%

basics

~20 s

An immutable String can never change after it is created. Methods like concat or replace do not edit the original; they build and return a brand-new String. Java made String immutable for safety, sharing, and speed.

open as a page

How does a Java String store its characters internally, and what was the original backing data structure?

level: juniorimportance: must knowfreq 60%

basics

~20 s

A String keeps its characters in a private array inside the String object. Originally that was a char array with two bytes per character. The String is immutable, so the array never changes after the String is created.

open as a page

What is the difference between String s = "hi" and String s = new String("hi") in terms of object creation and identity?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A string literal like "hi" is stored in a shared pool, so equal literals point to the same object. new String("hi") always makes a brand-new object on the heap, separate from the pool, even if the text is identical.

open as a page

What is a text block in Java, and how do you declare one?

level: juniorimportance: must knowfreq 70%

basics

~10 s

A text block is a multi-line string literal that starts and ends with three double-quotes ("""). It lets you write text spanning several lines without escaping every newline or quote.

open as a page

How does String.split() work, and why is its delimiter argument a regular expression with surprising trailing-empty-string behavior?

level: middleimportance: must knowfreq 74%

basics

~10 s

split breaks a string into an array of pieces around a delimiter. The delimiter is a regex, not plain text, and by default empty pieces at the end of the result are dropped.

open as a page

What is the core difference between StringBuilder and StringBuffer, and which should you reach for by default?

level: middleimportance: must knowfreq 85%

basics

~20 s

They have the same API and do the same job: build strings in a mutable buffer. StringBuffer's methods are synchronized (thread-safe but slower); StringBuilder's are not (faster). Use StringBuilder by default; use StringBuffer only when one instance is shared across threads.

open as a page

How does the Java compiler translate the + string concatenation operator, and how has that changed in modern Java?

level: middleimportance: must knowfreq 68%

basics

~20 s

The + you write is not a real method on String. The compiler turns it into bytecode that actually builds the joined string. Older Java used a StringBuilder behind the scenes; modern Java uses a special hook called invokedynamic.

open as a page

How does String immutability enable the string pool, and how do literals differ from new String(...)?

level: middleimportance: must knowfreq 78%

basics

~20 s

Because strings never change, Java can safely store one shared copy of each literal in a pool and reuse it. Two equal literals are the same object. But new String("x") forces a separate object on the heap, even though its text is equal.

open as a page

What are compact strings introduced in Java 9, and how do they change String's internal layout?

level: middleimportance: must knowfreq 55%

basics

~20 s

Since Java 9, String stores text in a byte array instead of a char array, with a small flag saying whether the bytes are Latin-1 (one byte per char) or UTF-16 (two bytes). Pure-ASCII text now uses half the memory.

open as a page

How does the compiler strip incidental whitespace from a text block, and what determines the minimal indentation?

level: middleimportance: must knowfreq 68%

basics

~20 s

The compiler removes the common leading whitespace shared by every non-blank line and the closing delimiter line. So you can indent the block to match your code, and that indentation is dropped from the result.

open as a page

Given that Java Strings are immutable, what are the performance implications of the String API in loops, and how do StringBuilder, the '+' operator, and intern() fit in?

level: seniorimportance: must knowfreq 62%

basics

~20 s

Every String operation creates a new String, so building one piece-by-piece with '+' in a loop is slow because it makes many throwaway strings. Use StringBuilder to build in a loop; it edits a buffer in place.

open as a page

Why is using + to build a string inside a loop a performance pitfall, and what should you use instead?

level: seniorimportance: must knowfreq 72%

basics

~20 s

Each + in a loop makes a whole new string and copies all the characters so far. Over many iterations that copying gets very expensive. Use a StringBuilder and append in the loop, then call toString() once at the end.

open as a page

How does StringBuilder's fluent method chaining work, and what does a method like append or reverse return?

level: juniorimportance: should knowfreq 48%

basics

~10 s

Mutating methods like append, insert, and reverse change the builder in place and then return the same builder object (this). Because they return the builder, you can chain calls one after another, like sb.append("a").append("b").reverse().

open as a page

What do String.format() and String.join() do, and when would you choose each over manual concatenation?

level: middleimportance: should knowfreq 58%

basics

~10 s

String.format() builds a string from a template with placeholders like %s and %d filled by arguments. String.join() glues several strings together with a separator between them.

open as a page

What is compile-time constant folding of string concatenation, and when does it apply?

level: middleimportance: should knowfreq 45%

basics

~20 s

If you join strings that are all known at compile time (literals or final constants), the compiler joins them itself and stores the single finished string. No work happens at runtime, and the result is a pooled, interned string literal.

open as a page

How does String.compareTo() work, and how does it differ from equals() for ordering and sorting strings?

level: middleimportance: should knowfreq 60%

basics

~20 s

equals() answers a yes/no question: are two strings the same text? compareTo() answers an ordering question: which comes first alphabetically. It returns a negative number, zero, or a positive number, and is what sorting uses to put strings in order.

open as a page

What is the Java String pool (intern pool), and how does it affect == comparisons between strings?

level: middleimportance: should knowfreq 70%

basics

~20 s

The string pool is a cache of String objects that Java reuses for identical string literals. Because identical literals share one pooled object, == returns true for them. Strings built at runtime are not pooled, so == returns false for them.

open as a page

Why does String immutability give thread-safety for free and allow the hash code to be cached?

level: middleimportance: should knowfreq 62%

basics

~20 s

Since a String never changes, many threads can read it at once with no locks — there is nothing to corrupt. And because its characters are fixed, its hash code is computed once and stored, so repeated hashing (e.g. as a HashMap key) is cheap and consistent.

open as a page

What does the String.intern() method do, and how does it interact with the string pool?

level: middleimportance: should knowfreq 62%

basics

~20 s

intern() looks in the shared string pool for a string with the same value. If one is there, it returns that pooled object; if not, it adds the current string to the pool and returns it. The result is a canonical reference equal-valued strings can share.

open as a page

How do you control whether a text block ends with a newline, and how do the \s and trailing \ escapes work?

level: middleimportance: should knowfreq 58%

basics

~20 s

If the closing """ is on its own line, the result ends with a newline; put the closing """ right after the last character to drop it. The \s escape preserves a trailing space, and a trailing backslash joins a line to the next (no newline).

open as a page

What is the difference between String.chars() and String.codePoints(), and why does it matter for characters like emoji?

level: seniorimportance: should knowfreq 40%

basics

~20 s

chars() gives a stream of UTF-16 code units, so a character stored as two units (like an emoji) shows up as two values. codePoints() gives a stream of full Unicode characters, so each emoji is one value.

open as a page

How does a StringBuilder grow its internal buffer, and when would you pre-size its capacity?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A StringBuilder wraps a char array with a default capacity of 16. When you append past capacity it allocates a bigger array (about double plus two) and copies everything over. If you know roughly the final size, pass it to the constructor so it avoids those reallocations.

open as a page

How would you decide between +, StringBuilder, and String.join/StringJoiner/Collectors.joining for a given concatenation task?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Use + for a few fixed pieces in one statement. Use StringBuilder when building up text in a loop. Use String.join or Collectors.joining when gluing a collection together with a separator.

open as a page

When you use a String as a key in a HashMap, what role do equals() and hashCode() play, and why would using == instead break lookups?

level: seniorimportance: should knowfreq 55%

basics

~20 s

HashMap finds a key by its hashCode() to pick a bucket, then uses equals() to match within that bucket. String overrides both consistently, so lookups work by content. If maps used == they would fail whenever a key was rebuilt at runtime instead of being the same object.

open as a page

How do operations like substring, replace, and concat behave given that String is immutable, and what are the performance implications?

level: seniorimportance: should knowfreq 58%

basics

~10 s

Every transforming method returns a brand-new String and leaves the original alone. Doing this repeatedly (e.g. building text with + in a loop) creates many throwaway objects, so use StringBuilder for heavy building.

open as a page

showing 1–30 of 38