How does String.substring() work in Java, including its index arguments and what happens to the original string?
answer
- begin inclusive, end exclusive → length = end - begin
- zero-based, counts chars (UTF-16 units)
- immutable: returns new String, original unchanged
- out of range → StringIndexOutOfBoundsException
- since 7u6 always copies (no shared array leak)
basics
~10 ssubstring(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.
solid answer
~40 sString.substring(beginIndex) returns the text from beginIndex to the end; substring(beginIndex, endIndex) returns characters from beginIndex inclusive to endIndex exclusive, so the length is endIndex - beginIndex. Indices are zero-based and count by char (UTF-16 code units), not by visible character. substring never mutates the original — strings are immutable, so it returns a brand-new String. Out-of-range indices (negative, begin > end, or beyond length) throw StringIndexOutOfBoundsException. substring(0) or substring(0, length()) returns an equal string. Since Java 7u6 substring always copies the backing char array, so the result no longer keeps the whole original array alive — earlier JDKs shared the array, which could cause memory leaks when you kept a tiny substring of a huge string.
go deeper
Knows begin is inclusive, end exclusive, indices are zero-based, and that the original string is unchanged because strings are immutable.
Can state the valid index range, predict the exception for bad indices, and knows substring returns a new String that must be assigned.
Explains the half-open interval rationale, the 7u6 copy-vs-share history and its memory implications, and the UTF-16 code-unit caveat for surrogate pairs.
Reasons about API design (half-open intervals, immutability guarantees), performance trade-offs of copy-on-substring, and guides teams on code-point-correct text handling at scale.
## What a String is In Java a `String` is an **immutable** sequence of characters. Immutable means once created it can never be changed; any method that 'modifies' a string actually returns a **new** string and leaves the original untouched. Internally a String is backed by an array of characters (a `char[]` in older JDKs, a `byte[]` since Java 9's compact strings). Each position is addressed by an **index** starting at **0** (the first character is index 0, the second is index 1, and so on). ## What substring does `substring` extracts a contiguous slice of the string. It has two forms: - `s.substring(beginIndex)` — returns everything from `beginIndex` to the end of the string. - `s.substring(beginIndex, endIndex)` — returns the characters starting at `beginIndex` (**inclusive**) and ending just before `endIndex` (**exclusive**). 'Inclusive' means the character at that index IS included; 'exclusive' means the character at that index is NOT included. So the number of characters returned is exactly `endIndex - beginIndex`. This 'half-open interval' `[begin, end)` is the same convention used by most Java range APIs, which makes lengths easy to compute and makes adjacent ranges line up without overlap. Example: for `"hello"`, the indices are h=0, e=1, l=2, l=3, o=4. - `"hello".substring(1)` → `"ello"` - `"hello".substring(1, 3)` → `"el"` (indices 1 and 2; index 3 excluded) - `"hello".substring(0, 5)` → `"hello"` (a copy equal to the whole string) - `"hello".substring(5)` → `""` (an empty string — legal, begin equals length) ## Edge cases and exceptions The valid range is `0 <= beginIndex <= endIndex <= length()`. If you violate that — a negative index, `endIndex` past the end, or `beginIndex > endIndex` — Java throws `StringIndexOutOfBoundsException` (a subclass of `IndexOutOfBoundsException`, an unchecked `RuntimeException`). Note `beginIndex == length()` is allowed and yields `""`. ## Immutability — the original never changes Because strings are immutable, `substring` cannot alter the source. It returns a new String, so code like `s.substring(2)` on its own does nothing useful unless you assign the result. This is a classic beginner trap: `s.substring(2);` discards the result. ## char counting vs. visible characters Indices count **char values**, which are UTF-16 **code units**, not necessarily whole visible symbols. Characters outside the Basic Multilingual Plane (e.g. many emoji, like 😀) are stored as a **surrogate pair** of two chars. Slicing in the middle of such a pair produces a malformed half-character. For text that may contain such characters, prefer code-point-aware logic. ## Modern copy behavior (the memory-leak fix) Before JDK 7u6, `substring` did **not** copy: the returned String shared the original `char[]` and just stored an offset and count. That made `substring` O(1) but meant holding a 3-character substring of a 10-MB string kept the whole 10 MB alive — a subtle memory leak. Since 7u6, `substring` **always copies** the relevant characters into a fresh array, so the result is independent of the source. The trade-off is that `substring` is now O(n) in the length of the slice, but memory behavior is intuitive. The old `new String(s.substring(...))` workaround to force a copy is no longer needed.
- Why was the pre-7u6 sharing behavior considered a problem?A small substring kept a reference to the entire original char[], so a tiny slice of a huge string prevented that huge array from being garbage collected — a memory leak. The 7u6 change copies the slice, breaking that retention.
- What does "abc".substring(3) return, and does substring(4) work?substring(3) returns an empty string "" (begin == length is allowed). substring(4) throws StringIndexOutOfBoundsException because 4 > length 3.
saying these in an interview costs you the question
- Saying substring modifies the original string
- Thinking endIndex is inclusive (off-by-one)
- Believing substring still shares the backing array (pre-7u6 behavior)
- Forgetting to assign the result, expecting in-place change
- Assuming one index always equals one visible character (surrogate pairs)