What is the time complexity of the String#substring()
method in Java?

- 18,337
- 10
- 57
- 90

- 7,032
- 16
- 57
- 70
7 Answers
New answer
As of update 6 within Java 7's lifetime, the behaviour of substring
changed to create a copy - so every String
refers to a char[]
which is not shared with any other object, as far as I'm aware. So at that point, substring()
became an O(n) operation where n is the numbers in the substring.
Old answer: pre-Java 7
Undocumented - but in practice O(1) if you assume no garbage collection is required, etc.
It simply builds a new String
object referring to the same underlying char[]
but with different offset and count values. So the cost is the time taken to perform validation and construct a single new (reasonably small) object. That's O(1) as far as it's sensible to talk about the complexity of operations which can vary in time based on garbage collection, CPU caches etc. In particular, it doesn't directly depend on the length of the original string or the substring.

- 1,421,763
- 867
- 9,128
- 9,194
-
16+1 for "undocumented", which is an unfortunate weakness of the API. – Raedwald Jan 13 '11 at 12:43
-
11It's not weakness. If behavior is documented, and implementation details are not, it allows faster implementations in the future. In general, Java often defines behavior and lets implementations decide what's best way. In other words - you should not care, after all, it's Java ;-) – peenut Jan 13 '11 at 15:37
-
2Good point peenut, even if I hardly believe they will ever manage to make this one faster than O(1). – abahgat Aug 12 '11 at 16:04
-
12No, something like this should be documented. A developer should be aware, just in case he plans to take a small substring of a large string, expecting the larger string to be garbage collected like it would be in .NET. – Qwertie Jun 22 '12 at 21:10
-
1It's worth noting that although performance is worse, [this change was made to address a gc leak](http://twistedoakstudios.com/blog/Post8147_referencing-substrings-faster-without-leaking-memory) – Krease Oct 01 '15 at 23:59
-
Is it linear in the number of characters of the original string or in the number of copied characters? – Ivaylo Toskov Feb 13 '16 at 18:17
-
1@IvayloToskov: The number of copied characters. – Jon Skeet Feb 14 '16 at 09:12
-
We should update this answer to be more exact - it is confusing because usually when we say O(n) for a string, it refers to the number of chars in the string, whereas this is O(m) where m is length of the substring. Thus, if we have stringNChars.substring(a,a+m) (where m is length) then it is O(m), not O(n). – user151975 Jul 26 '17 at 21:37
-
1http://java-performance.info/changes-to-string-java-1-7-0_06/ – Bhaskar13 Jan 07 '21 at 19:02
It was O(1) in older versions of Java - as Jon stated, it just created a new String with the same underlying char[], and a different offset and length.
However, this has actually changed started with Java 7 update 6.
The char[] sharing was eliminated, and the offset and length fields were removed. substring() now just copies all the characters into a new String.
Ergo, substring is O(n) in Java 7 update 6

- 22,624
- 6
- 36
- 37
-
2+1 This is indeed the case in recent Sun Java and OpenJDK versions. GNU Classpath (and others, I assume) are still using the old paradigm. Unfortunately there seems to be a bit of intellectual inertia w.r.t. this. I still see posts in 2013 recommending various approaches based on the assumption that substrings use a shared `char[]`... – thkala Feb 04 '13 at 18:40
-
14So new version no longer have O(1) complexity. Curious to know is there any alternative way to implement substring in O(1)? String.substring is an extremely useful method. – Yitong Zhou Jun 05 '13 at 00:39
It's now linear complexity. This is after fixing a memory leak issue for substring.
So from Java 1.7.0_06 remember that String.substring now has a linear complexity instead of a constant one.

- 30,738
- 21
- 105
- 131

- 131
- 1
- 3
Adding proof to Jon's answer. I had same doubt and wanted to check whether length of string has any effects on substring function. Written following code to check on which parameter substring actually depends on.
import org.apache.commons.lang.RandomStringUtils;
public class Dummy {
private static final String pool[] = new String[3];
private static int substringLength;
public static void main(String args[]) {
pool[0] = RandomStringUtils.random(2000);
pool[1] = RandomStringUtils.random(10000);
pool[2] = RandomStringUtils.random(100000);
test(10);
test(100);
test(1000);
}
public static void test(int val) {
substringLength = val;
StatsCopy statsCopy[] = new StatsCopy[3];
for (int j = 0; j < 3; j++) {
statsCopy[j] = new StatsCopy();
}
long latency[] = new long[3];
for (int i = 0; i < 10000; i++) {
for (int j = 0; j < 3; j++) {
latency[j] = latency(pool[j]);
statsCopy[j].send(latency[j]);
}
}
for (int i = 0; i < 3; i++) {
System.out.println(
" Avg: "
+ (int) statsCopy[i].getAvg()
+ "\t String length: "
+ pool[i].length()
+ "\tSubstring Length: "
+ substringLength);
}
System.out.println();
}
private static long latency(String a) {
long startTime = System.nanoTime();
a.substring(0, substringLength);
long endtime = System.nanoTime();
return endtime - startTime;
}
private static class StatsCopy {
private long count = 0;
private long min = Integer.MAX_VALUE;
private long max = 0;
private double avg = 0;
public void send(long latency) {
computeStats(latency);
count++;
}
private void computeStats(long latency) {
if (min > latency) min = latency;
if (max < latency) max = latency;
avg = ((float) count / (count + 1)) * avg + (float) latency / (count + 1);
}
public double getAvg() {
return avg;
}
public long getMin() {
return min;
}
public long getMax() {
return max;
}
public long getCount() {
return count;
}
}
}
Output on execution in Java 8 is:
Avg: 128 String length: 2000 Substring Length: 10
Avg: 127 String length: 10000 Substring Length: 10
Avg: 124 String length: 100000 Substring Length: 10
Avg: 172 String length: 2000 Substring Length: 100
Avg: 175 String length: 10000 Substring Length: 100
Avg: 177 String length: 100000 Substring Length: 100
Avg: 1199 String length: 2000 Substring Length: 1000
Avg: 1186 String length: 10000 Substring Length: 1000
Avg: 1339 String length: 100000 Substring Length: 1000
Proving substring function depends on length of substring requested not on the length of the string.

- 103
- 1
- 5
-
2I hate to be the one ruining the party, but this is not "proof" but an indication – akiva Jun 03 '21 at 03:36
O(1) because no copying of the original string is done, it just creates a new wrapper object with different offset information.

- 14,729
- 3
- 53
- 55
Judge for yourself from following, but Java's performance drawbacks lie somewhere else, not here in substring of a string. Code:
public static void main(String[] args) throws IOException {
String longStr = "asjf97zcv.1jm2497z20`1829182oqiwure92874nvcxz,nvz.,xo" +
"aihf[oiefjkas';./.,z][p\\°°°°°°°°?!(*#&(@*&#!)^(*&(*&)(*&" +
"fasdznmcxzvvcxz,vc,mvczvcz,mvcz,mcvcxvc,mvcxcvcxvcxvcxvcx";
int[] indices = new int[32 * 1024];
int[] lengths = new int[indices.length];
Random r = new Random();
final int minLength = 6;
for (int i = 0; i < indices.length; ++i)
{
indices[i] = r.nextInt(longStr.length() - minLength);
lengths[i] = minLength + r.nextInt(longStr.length() - indices[i] - minLength);
}
long start = System.nanoTime();
int avoidOptimization = 0;
for (int i = 0; i < indices.length; ++i)
//avoidOptimization += lengths[i]; //tested - this was cheap
avoidOptimization += longStr.substring(indices[i],
indices[i] + lengths[i]).length();
long end = System.nanoTime();
System.out.println("substring " + indices.length + " times");
System.out.println("Sum of lengths of splits = " + avoidOptimization);
System.out.println("Elapsed " + (end - start) / 1.0e6 + " ms");
}
Output:
substring 32768 times Sum of lengths of splits = 1494414 Elapsed 2.446679 ms
If it is O(1) or not, depends. If you just reference same String in memory, then imagine very long String, you make substring and stop referencing long one. Wouldn't be nice to release memory for long one?

- 3,366
- 23
- 24
Before Java 1.7.0_06: O(1).
After Java 1.7.0_06: O(n). This was changed, because of memory leak. After the fields offset
and count
were removed from String, the substring implementation became O(n).
For more details, please refer to : http://java-performance.info/changes-to-string-java-1-7-0_06/

- 1,176
- 11
- 9