Arrays & Strings in Java
The Java mechanics under every array and string solution — and the small traps that cost big marks.
▶ guided lesson⚡ 1-minute summary↻ 14 recall cards
before this: Java Memory
01 · why it exists
Why this exists, and the weight it carries
since1995
Java arrays (int[], int[][])
James Gosling and the Sun Java team
In C, an array is just a pointer to memory, and nothing stops you writing past its end. Java's October 1995 white paper made arrays real objects and promised that every access is checked against the array's bounds. The Java Language Specification still says it: all array accesses are checked at run time, and a bad index throws ArrayIndexOutOfBoundsException. Fun fact: that 1995 white paper wrote myPoints.length() for arrays — the shipped language made it a plain field, length, the same mix-up learners make today.
the problem it solvedOut-of-bounds writes in C silently corrupt memory and crash much later (the white paper calls it the "delayed-crash" syndrome). Checked arrays turn that into an immediate, named exception.
its jobs today
- The storage inside
ArrayList,ArrayDeque,PriorityQueueandHashMap's bucket table — each keeps a plain array inside and moves to a bigger one to grow. - Fixed-size tables in DSA code: letter counts (
int[26]), DP tables (int[n + 1]), visited flags (boolean[n]), grids (int[m][n]).
the weight it carries
Illustrative: 10 lakh (1,000,000) int scores. An int[] takes 4 B × 1,000,000 ≈ 4 MB in one block, and scores[750000] is one address calculation plus a bounds check — never a search.
without it
Every count, table and grid would need a map or a list of objects: several times the memory and a pointer hop on every read.
when · why · how
- when
- The size is known (or bounded) up front and you index by position: counts, DP tables, grids, flags.
- why
- O(1) read and write by index, no boxing, contiguous memory. The cost: the length is fixed, so growing means an O(n) copy.
- how
int[] a = new int[n];(zeros),int[] b = {1, 2, 3};,int[][] g = new int[rows][cols];, loop withi < a.length.
since1998
java.util.Arrays helpers
Josh Bloch, Neal Gafter and John Rose (authors named in the JDK source)
JDK 1.2 shipped the Collections Framework and, with it, java.util.Arrays: static helpers so nobody has to hand-write sort, search, fill or equals for arrays again. Its javadoc still says "Since 1.2" and calls it a member of the Java Collections Framework. Today sort(int[]) is a Dual-Pivot Quicksort by Vladimir Yaroslavskiy, Jon Bentley and Joshua Bloch, and sorting objects uses a stable merge sort adapted from Tim Peters's list sort for Python (TimSort).
the problem it solvedHand-written copy, compare, fill and sort loops are where off-by-one bugs live. One tested call removes the bug and says what you mean.
its jobs today
Arrays.sortputs primitives in order before two pointers, binary search or greedy scans.fill,copyOf,copyOfRange,equalsandtoStringreplace loops you would otherwise write — and debug — by hand.
the weight it carries
Illustrative: sorting 10 lakh ints. A hand-written insertion sort does up to n²/2 = 5 × 10¹¹ comparisons. Arrays.sort does about n log₂ n ≈ 1,000,000 × 20 = 2 × 10⁷ steps — roughly 25,000× less work.
without it
Every solution would carry 10–20 extra lines of copy, fill and compare loops: more places for an off-by-one, and slower to write under pressure.
when · why · how
- when
- You need to sort, copy, fill, compare or print an array.
- why
- Tested and clear;
sortis O(n log n), the rest O(n). Know the traps:asListonint[], the exclusive end ofcopyOfRange,equalsvsdeepEquals. - how
Arrays.sort(a),Arrays.fill(a, -1),Arrays.copyOf(a, n),Arrays.copyOfRange(a, from, to),Arrays.equals(a, b),Arrays.toString(a).
since2004
Primitive vs boxed values
JSR 201 — spec leads Gilad Bracha and Joshua Bloch
Collections can only hold objects. Before Java 5 you wrapped every number yourself — list.add(new Integer(x)), then cast and call .intValue() to read it back. JSR 201, final in September 2004 as part of J2SE 5.0 "Tiger", added autoboxing together with enums, the enhanced for loop and static import. Since then Java converts int ↔ Integer for you: convenient, and also the reason the cost became invisible.
the problem it solvedWriting new Integer(x) and .intValue() around every collection call was noisy and easy to get wrong.
its jobs today
- Generics need objects:
List<Integer>,Map<Character, Integer>andPriorityQueue<Integer>all hold boxed values. Integer[]lets you sort with a Comparator (for example descending) —Arrays.sort(int[])can't take one.
the weight it carries
Illustrative, typical 64-bit JVM with compressed references: 10 lakh values outside the small −128..127 cache. int[]: 4 B × 1,000,000 ≈ 4 MB. Integer[] or ArrayList<Integer>: a 4 B reference + a 16 B Integer object each ≈ 20 MB — 5× the memory, plus a pointer hop per read (sizes as measured in Shipilëv's Java Objects Inside Out).
without it
Without boxing, collections couldn't hold numbers at all. Without primitives, every counter and DP table would pay that 5× memory and pointer-chasing cost.
when · why · how
- when
int[]for fixed-size numeric work.List<Integer>when the size grows or the API demands a List.Integer[]only to sort with a Comparator.- why
- Primitives are compact and fast. Boxed values are objects: more memory,
nullis possible, and==compares references. - how
list.add(x)boxes;int y = list.get(i)unboxes (and throws onnull). Convert with a loop orlist.stream().mapToInt(Integer::intValue).toArray().
since1995
String: immutable and pooled
James Gosling and the Sun Java team
In C a string is a char array anyone can overwrite. Java's October 1995 white paper made strings objects of two kinds: String for read-only (immutable) text and StringBuffer for text you want to change. The JLS adds the pool: equal string literals are interned so they share one object. Later changes kept the idea but changed the storage — since Java 7u6 (2012) substring copies instead of sharing the original's array, and since Java 9 (JEP 254) Latin-1 text takes 1 byte per char.
the problem it solvedText that can't change is safe to share, to cache and to use as a HashMap key — nobody can edit it behind your back.
its jobs today
HashMap/HashSetkeys: an immutable key can't change after it's stored, so it stays in the right bucket.- String literals in your code (a JSON key like
"id", a route like"/api/orders") share one pooled object instead of being copied. Strings built at run time (input,substring,+) are separate objects unless interned.
the weight it carries
Illustrative: a 1 lakh-char log line. "Changing" one character with String methods builds a new 100,000-char String — O(n) per edit. Go through toCharArray() once (100,000 chars copied) and every later edit is O(1).
without it
Any code holding a reference could change your text: map keys would end up in the wrong bucket, and sharing literals would be unsafe.
when · why · how
- when
- Reading text:
charAt,length(),equals,compareTo,substring. - why
- Reads are O(1) per char and safe to share. Every "change" (
trim,toUpperCase,replace,+) returns a NEW string — O(n) each. - how
- Compare with
s.equals(t), never==. Keep results:s = s.trim();. To edit, go throughchar[]orStringBuilder.
since2004
StringBuilder
Sun's JDK team (J2SE 5.0)
Java 1.0 already had a mutable StringBuffer, but its methods are synchronized for thread safety. J2SE 5.0 added StringBuilder: the same API with no synchronization, documented as the faster choice when a single thread builds the string — which is nearly always. Its internal array starts with room for 16 chars and grows to twice the old capacity plus 2 when full.
the problem it solvedBuilding text piece by piece without copying everything on every append.
its jobs today
- Building answers: joining path parts, encoding strings, reversing with
sb.reverse(). - Fast output in online assessments: collect every line in one builder, print once.
the weight it carries
Computed for a 100,000-char string built one char at a time: s += c writes 1 + 2 + … + 100,000 = 5,000,050,000 chars. StringBuilder writes 100,000, copies 147,412 while growing (16 → 34 → 70 → …) and 100,000 more in toString() = 347,412 — about 14,000× less.
without it
Every loop that builds a string would be O(n²): fine at 100 chars, a timeout at 10⁵.
when · why · how
- when
- Any loop that builds a String, and any output with many lines.
- why
appendis O(1) amortised — the array roughly doubles, so the growth copying stays under about 2n — andtoString()is one O(n) copy at the end.- how
StringBuilder sb = new StringBuilder(); sb.append(x).append(' '); String s = sb.toString();Alsoreverse(),setCharAt(i, c),deleteCharAt(sb.length() - 1).
since1995
char arithmetic
Java's designers, building on Unicode
Java made char a number from day one: the October 1995 white paper already calls it an unsigned 16-bit Unicode value, 0 to 65,535, and today's JLS defines it as a 16-bit unsigned integer holding UTF-16 code units. Unicode's Basic Latin block (U+0000–U+007F) keeps the old ASCII codes, so 'a'..'z' are the consecutive numbers 97..122 and '0'..'9' are 48..57. That is the whole trick: c - 'a' is plain subtraction, and the answer is an index.
the problem it solvedTurning letters and digits into array indexes and numbers without a lookup table or a HashMap.
its jobs today
int[26]letter counts for anagrams, first-unique and permutation checks.- Parsing digits by hand (
value = value * 10 + (c - '0')) and Caesar-style shifts ((char) ('a' + (c - 'a' + k) % 26)).
the weight it carries
Illustrative: count the letters of a 1 lakh-char string. int[26]: 100,000 array increments and 26 × 4 = 104 bytes of counters. HashMap<Character, Integer>: 100,000 hash lookups plus boxed keys and values — the same Big-O, several times the work per step.
without it
Letter counting would need a HashMap (hashing and boxing on every char) or a 26-way switch.
when · why · how
- when
- The input is limited to lowercase letters, digits or ASCII.
- why
- O(1) per character with no hashing, and O(1) space (26 or 128 counters).
- how
count[c - 'a']++,int d = c - '0',char back = (char) ('a' + i),Character.toLowerCase(c). Mixed case: normalise first or useint[128].
02 · plain english
The idea in 30 seconds
An int[] is a fixed row of numbered slots. A String is a sealed row of characters: you can read it, never change it.
Almost every DSA answer reads, copies or builds one of these. Their costs and traps (length vs length(), ==, += in a loop) decide whether your code is O(n) or O(n²), right or wrong.
Here you learn the tools. The array patterns (prefix sums, Kadane, two pointers) come in Stage 3.
picture this
A String is a PDF someone sent you: you can read any character instantly, but you can't edit it — every "change" exports a whole new PDF. A StringBuilder is the Word file you keep typing into, then export once at the end.
03 · remember
What you must know
new int[n]gives n slots already filled with defaults:0,false,'\u0000', ornullfor objects. The length is fixed forever.- Three different sizes:
arr.length(a field, for arrays),s.length()(a method, for strings),list.size()(a method, for collections). ==on arrays or strings asks "is this the same object?". For contents useArrays.equals(a, b)ands.equals(t). To print, useArrays.toString(a).int[]stores raw numbers side by side.Integer[]andList<Integer>store references to boxed objects — more memory, slower, andnullis possible.- A
Stringnever changes.s += ccopies the whole string every time → O(n²) in a loop.StringBuilder.appendis O(1) amortised → O(n) total. - A
charis a 16-bit number.c - 'a'turns 'a'..'z' into 0..25 and(char) ('a' + i)turns it back — that's how anint[26]replaces a HashMap. int[][]is an array of row arrays:grid.lengthrows,grid[r].lengthcolumns in row r. Rows may differ in length (jagged).- Library calls are not free:
Arrays.copyOf,toCharArray,substringandsplitall copy — O(n) each.
04 · see it
Play with it in 3D
Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.
s += c vs StringBuilder — count the copies
Left bar: every `+=` builds a brand-new string. Right bar: the builder writes one char, and only copies when its array is full.
Char arithmetic — letters become array indexes
`c - 'a'` maps each letter to a counter. Pass 1 counts, pass 2 finds the first letter with count 1.
2D and jagged arrays — rows of rows
`accounts[r]` is a whole row array. Each row can have its own length, so the inner loop asks that row.
05 · cost
Time & space, explained
| Time | Extra space | why | |
|---|---|---|---|
| new int[n] | O(n) | O(n) | Java fills every slot with the default value before you see it. |
| a[i] read / write, a.length | O(1) | O(1) | Address arithmetic plus a bounds check; the length is a stored field. |
| Arrays.fill / equals / toString | O(n) | O(1) / O(1) / O(n) | One pass over the elements; toString builds a string of all of them. |
| Arrays.copyOf / copyOfRange / clone | O(length copied) | O(length copied) | A new array is allocated and filled — never a free view. |
| Arrays.sort(int[]) | O(n log n) | sorts in place | Dual-Pivot Quicksort; the javadoc promises O(n log n) on all data sets. |
| Arrays.sort(Integer[], comparator) | O(n log n) | up to n/2 references | A stable merge sort (TimSort) on objects; the temp buffer is what the extra space pays for. |
| Arrays.asList(T[]) | O(1) | O(1) | A fixed-size List view over the same array — nothing is copied. |
| Time | Notes | why | |
|---|---|---|---|
| s.charAt(i), s.length() | O(1) | read only | The characters sit in an internal array with a known length. |
| s + t (one expression) | O(|s| + |t|) | a new String | Both strings are copied into a fresh one. |
| s += c, n times in a loop | O(n²) | n new Strings | Step k copies k chars: 1 + 2 + … + n = n(n + 1)/2. |
| sb.append(c), n times, then toString() | O(n) | O(1) amortised per append | The array grows to 2 × old + 2 when full, so growth copying stays under about 2n. |
| s.toCharArray(), s.substring(a, b) | O(n), O(b − a) | both copy | Both allocate new storage. substring stopped sharing the original's array in Java 7u6. |
| s.split(regex), s.trim() | O(n) | new strings | One scan, plus a new String for every piece. |
| s.equals(t), s.compareTo(t) | O(n) | no copy | Char by char until the first difference (or the end). |
| Integer.parseInt(s), String.valueOf(x) | O(digits) | parse / format | One pass over the digits. |
| int[] | Integer[] | ArrayList<Integer> | why | |
|---|---|---|---|---|
| New slots start as | 0 | null | — (starts empty) | Primitives default to 0; references default to null. |
| Memory per value (typical 64-bit JVM) | 4 B | 4 B ref + 16 B object | 4 B ref + 16 B object + spare capacity | Each boxed value is its own object (values −128..127 are cached and shared). |
| Can it grow? | no | no | yes — add is O(1) amortised | Arrays are fixed; ArrayList copies into a bigger array when full. |
| Sort with a Comparator? | no | yes | yes (list.sort) | Comparators work on objects only. |
| Main danger | overflow | NPE when unboxing null | NPE; remove(int) vs remove(Object) | Boxed types bring null and reference-equality problems. |
06 · java
Raw vs Collections Framework
Creating arrays: sizes and default values
class Create { static String defaults() { int[] counts = new int[3]; // [0, 0, 0] boolean[] seen = new boolean[2]; // [false, false] char[] letters = new char[2]; // two '\u0000' chars (code 0), not spaces String[] names = new String[2]; // [null, null]: no String objects exist yet int[] primes = {2, 3, 5, 7}; // literal: the length comes from the values return Arrays.toString(counts) + " " + Arrays.toString(seen) + " " + (int) letters[0] + " " + Arrays.toString(names) + " " + primes.length; } static String threeSizes(int[] arr, String s, List<Integer> list) { return arr.length + " " + s.length() + " " + list.size(); // field, method, method } static int lastIndex(int[] arr) { return arr.length - 1; // valid indices are 0 .. length - 1 }}- Arrays are always filled: numbers with
0, booleans withfalse, chars with'\u0000'(code 0, not a space), objects withnull. - Valid indexes are
0 .. a.length - 1.a[a.length]throwsArrayIndexOutOfBoundsException— Java checks every access. new int[0]is legal and useful (an empty answer).new int[-1]throwsNegativeArraySizeException.
Copying and comparing arrays
class ByHand { static int[] copyOf(int[] a, int newLength) { int[] copy = new int[newLength]; // new slots start at 0 for (int i = 0; i < Math.min(a.length, newLength); i++) copy[i] = a[i]; return copy; } static boolean sameContents(int[] a, int[] b) { if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) if (a[i] != b[i]) return false; return true; }}class Helpers { static String demo() { int[] a = {5, 2, 9, 1}; int[] longer = Arrays.copyOf(a, 6); // [5, 2, 9, 1, 0, 0]: pads with 0 int[] middle = Arrays.copyOfRange(a, 1, 3); // [2, 9]: from inclusive, to exclusive Arrays.sort(a); // [1, 2, 5, 9] in place; returns nothing int[] filled = new int[4]; Arrays.fill(filled, -1); // [-1, -1, -1, -1] return Arrays.toString(longer) + " " + Arrays.toString(middle) + " " + Arrays.toString(a) + " " + Arrays.toString(filled); } static String equality() { int[] x = {1, 2, 3}, y = {1, 2, 3}; int[][] g = {{1, 2}, {3}}, h = {{1, 2}, {3}}; return (x == y) + " " + x.equals(y) + " " + Arrays.equals(x, y) // false false true + " " + Arrays.equals(g, h) + " " + Arrays.deepEquals(g, h); // false true } static String printing() { int[] x = {1, 2}; int[][] g = {{1, 2}, {3}}; String junk = String.valueOf(x); // what println(x) shows: "[I@" + a hash return Arrays.toString(x) + " " + Arrays.deepToString(g) + " " + junk.startsWith("[I@"); }}int[] b = a;copies nothing — it's a second name for the same array. Copy witha.clone()orArrays.copyOf(a, a.length).Arrays.copyOfRange(a, from, to):fromis inclusive,tois exclusive — the same rule assubstring.Arrays.equalscompares contents; for 2D arrays useArrays.deepEqualsandArrays.deepToString.Arrays.sort(a)sorts in place and returns nothing (void) — don't writea = Arrays.sort(a).
The Arrays.asList trap
class AsListTrap { static String demo() { int[] primitives = {3, 1, 2}; List<int[]> wrong = Arrays.asList(primitives); // ONE element: the whole int[] Integer[] boxed = {3, 1, 2}; List<Integer> view = Arrays.asList(boxed); // 3 elements, backed by the array view.set(0, 9); // writes through: boxed[0] is now 9 boolean addFails; try { view.add(4); // fixed size: no add, no remove addFails = false; } catch (UnsupportedOperationException e) { addFails = true; } List<Integer> real = new ArrayList<>(Arrays.asList(boxed)); // a real, growable copy real.add(4); return wrong.size() + " " + view.size() + " " + boxed[0] + " " + addFails + " " + real; }}- Generics only hold objects, so
Arrays.asList(int[])sees ONE object: the array itself →List<int[]>of size 1. Arrays.asList(Integer[])is a fixed-size view:setwrites through to the array,add/removethrowUnsupportedOperationException.List.of(...)is stricter still: fully immutable, and it rejectsnull. Need a growable list?new ArrayList<>(Arrays.asList(...)).
2D and jagged arrays
class TwoD { static String rectangle() { int[][] grid = new int[2][3]; // 2 rows × 3 columns, all 0 grid[1][2] = 7; // row 1, column 2 int rows = grid.length, cols = grid[0].length; return rows + "x" + cols + " " + Arrays.deepToString(grid); } static int[][] jagged(int rows) { int[][] tri = new int[rows][]; // rows exist, but each row is still null for (int r = 0; r < rows; r++) tri[r] = new int[r + 1]; // row r gets r + 1 slots return tri; } static int countCells(int[][] grid) { int cells = 0; for (int[] row : grid) cells += row.length; // use THIS row's length, never grid[0].length return cells; } static int[][] deepCopy(int[][] g) { int[][] copy = new int[g.length][]; for (int r = 0; r < g.length; r++) copy[r] = g[r].clone(); // copy every row return copy; // g.clone() alone would share the rows }}new int[2][3]makes an array of 2 references, each pointing to its ownint[3]row.grid[r][c]= row first, then column.new int[rows][]leaves every rownulluntil you assigngrid[r] = new int[len]— that's how jagged arrays (like a triangle) are built.grid.clone()copies only the row references: both grids share rows. Clone each row for a real copy.- Walk row by row (
router,cinner). Each row is contiguous memory, so this order is also the cache-friendly one.
int[] vs Integer[] vs List<Integer>
class Boxing { static String defaults() { int[] primitives = new int[3]; // [0, 0, 0]: raw numbers side by side Integer[] boxed = new Integer[3]; // [null, null, null]: references, no objects yet List<Integer> list = new ArrayList<>(); // empty; grows as you add for (int i = 0; i < 3; i++) list.add(i * 10); // autoboxing: int -> Integer boolean unboxingNullThrows = false; try { int first = boxed[0]; // auto-unboxing null -> NullPointerException list.add(first); } catch (NullPointerException e) { unboxingNullThrows = true; } return Arrays.toString(primitives) + " " + Arrays.toString(boxed) + " " + list + " " + unboxingNullThrows; } static int[] toIntArray(List<Integer> list) { int[] out = new int[list.size()]; for (int i = 0; i < out.length; i++) out[i] = list.get(i); // unboxes each element return out; // or: list.stream().mapToInt(Integer::intValue).toArray() } static Integer[] sortDescending(int[] nums) { Integer[] boxed = new Integer[nums.length]; for (int i = 0; i < nums.length; i++) boxed[i] = nums[i]; Arrays.sort(boxed, Collections.reverseOrder()); // a Comparator needs objects, not int return boxed; }}list.add(x)silently boxesint→Integer;int y = list.get(i)silently unboxes — and throwsNullPointerExceptiononnull.- Converting between
int[]andList<Integer>is always an O(n) loop (or a stream doing the same loop). Arrays.sort(int[])takes no Comparator. For descending order, sort ascending and read backwards, or box intoInteger[].
Strings never change (and the string pool)
class Immutable { static String demo() { String a = "upi"; // a literal lives in the string pool String b = "upi"; // same literal -> the SAME pooled object String c = new String("upi"); // forces a separate object on the heap String d = c.intern(); // asks the pool for its copy String upper = a.toUpperCase(); // a is untouched; a NEW string comes back return (a == b) + " " + (a == c) + " " + a.equals(c) + " " + (a == d) + " " + a + " " + upper; } static String trimBug(String s) { s.trim(); // BUG: the trimmed copy is thrown away return s; } static String trimFixed(String s) { return s.trim(); // keep the new string the method returns }}- Equal string literals share one pooled object, so
==sometimes works by accident. Strings fromnew, input orsubstringare separate objects. Always useequals. - Every "modifying" method —
trim,toUpperCase,replace,concat— returns a NEW string. Keep it:s = s.trim();. - Immutability is what makes strings safe as
HashMapkeys: a key can't change after it's stored.
Building strings: += vs StringBuilder
class Building { static String slow(int n) { String s = ""; for (int i = 0; i < n; i++) { s += (char) ('a' + i % 26); // a NEW string: copies all of s, then 1 more char } return s; } static String fast(int n) { StringBuilder sb = new StringBuilder(); // room for 16 chars to start for (int i = 0; i < n; i++) { sb.append((char) ('a' + i % 26)); // writes 1 char; when full, grows to 2 × old + 2 } return sb.toString(); // one final copy }}- One
a + b + cin a single expression is fine. The trap is+=repeated inside a loop: javac does not turn your loop into one builder. new StringBuilder()holds 16 chars; when full it grows to 2 × old + 2. Doubling keeps the total copying under about 2n.- Useful builder calls:
append,reverse(),setCharAt(i, c),deleteCharAt(sb.length() - 1),insert(0, x)(O(n) — it shifts). StringBufferis the older, synchronized twin. In interview code, alwaysStringBuilder.
Characters are numbers
class Chars { static int[] letterIndexes(String s) { int[] idx = new int[s.length()]; for (int i = 0; i < s.length(); i++) idx[i] = s.charAt(i) - 'a'; // 'a'->0 ... 'z'->25 return idx; } static char letterAt(int i) { return (char) ('a' + i); // int -> char needs a cast } static int digitValue(char c) { return c - '0'; // '7' -> 7 (its code is 55) } static String sortedLetters(String s) { char[] cs = s.toCharArray(); // a mutable COPY of the characters Arrays.sort(cs); return new String(cs); // or String.valueOf(cs); never cs.toString() } static String helpers() { return Character.isLetter('Q') + " " + Character.isDigit('7') + " " + Character.isLetterOrDigit('_') + " " + Character.toLowerCase('Q') + " " + Character.isUpperCase('Q') + " " + (int) 'a' + " " + ('a' + 1) + " " + (char) ('a' + 1); }}'a'is 97,'A'is 65,'0'is 48. Letters a–z are consecutive, soc - 'a'is an index 0..25.char + intgives anint:'a' + 1prints 98. Cast back with(char) ('a' + 1).toCharArray()makes a mutable copy — sort it, swap in it, thennew String(cs).cs.toString()prints junk like[C@….- Uppercase or mixed input?
Character.toLowerCase(c)first, or useint[128]indexed by the char itself.
Converting and parsing
class Convert { static String toText(int n, char c, char[] cs) { return String.valueOf(n) + "|" + String.valueOf(c) + "|" + String.valueOf(cs) + "|" + Integer.toString(n, 2) + "|" + n; // the last one: "" + n works too } static int parse(String s) { return Integer.parseInt(s.trim()); // trim first: " 42" alone would throw } static boolean parseThrows(String s) { try { Integer.parseInt(s); return false; } catch (NumberFormatException e) { return true; // "", " 42", "4.2", "2147483648" (too big) all throw } } static long parseBig(String s) { return Long.parseLong(s); // values beyond int's 2,147,483,647 }}String.valueOf(x)works for every type (andString.valueOf(char[])joins the chars).Integer.toString(n, 2)gives binary.Integer.parseIntthrowsNumberFormatExceptionon spaces, empty strings, decimals and values above 2,147,483,647. Trim input first.- Need bigger numbers?
Long.parseLonghandles up to about 9.2 × 10¹⁸.
split and trim traps
class SplitTrim { static String[] words(String line) { String t = line.trim(); // drop spaces at both ends first return t.isEmpty() ? new String[0] : t.split("\\s+"); // one or more whitespace chars } static String traps() { return "a b".split(" ").length // 3: ["a", "", "b"], an empty token between spaces + " " + " a b".split(" ").length // 3: a leading separator gives "" first + " " + "a,b,,".split(",").length // 2: trailing empty strings are dropped + " " + "a,b,,".split(",", -1).length // 4: limit -1 keeps them + " " + "1.2.3".split(".").length // 0: "." is regex for ANY char! + " " + "1.2.3".split("\\.").length // 3: escape it + " " + "x|y".split("\\|").length; // 2: | is regex too }}splittakes a regex..,|,+,*,?and$mean something there: escape them as"\\.","\\|".- Splitting on one space keeps empty tokens between double spaces and before a leading space.
trim()+split("\\s+")is the safe default for words. - Trailing empty strings are dropped (
"a,b,,"→ 2 pieces) unless you pass a limit of -1. trim()removes ASCII spaces and control chars at both ends;strip()(Java 11+) also removes Unicode spaces.
compareTo and substring
class CompareSub { static String compare() { return "apple".compareTo("banana") // -1: 'a' - 'b' at the first difference + " " + "rahul".compareTo("rahul") // 0: equal + " " + "Zomato".compareTo("swiggy") // -25: 'Z' (90) < 's' (115), capitals sort first + " " + "abc".compareTo("ab") // 1: same prefix, so the length decides + " " + "Priya".equalsIgnoreCase("PRIYA") + " " + "priya".compareToIgnoreCase("Rahul"); // -2: 'p' - 'r' } static String lastWord(String s) { int start = s.lastIndexOf(' ') + 1; // -1 + 1 = 0 when there is no space return s.substring(start); // copies s.length() - start chars }}a.compareTo(b)subtracts the first differing chars (or the lengths). Only the sign matters: negative = a first.- Uppercase sorts before lowercase ('Z' = 90 < 'a' = 97). Use
compareToIgnoreCasewhen case shouldn't matter. substring(a, b)copies b − a chars (since Java 7u6). Inside a loop, pass indexes around instead of cutting new strings.
| call | does | cost |
|---|---|---|
new int[n] / new int[r][c] | Zero-filled array / grid | O(n) / O(r·c) |
arr.length · s.length() · list.size() | Size of an array / a String / a collection | O(1) |
Arrays.fill(a, v) | Set every slot to v | O(n) |
Arrays.copyOf(a, len) / copyOfRange(a, from, to) | New array; pads with 0 or null; to is exclusive | O(len) |
Arrays.equals(a, b) / Arrays.deepEquals(g, h) | Compare contents of 1D / nested arrays | O(n) |
Arrays.toString(a) / Arrays.deepToString(g) | Readable text for debugging | O(n) |
Arrays.sort(a) / Arrays.sort(boxed, cmp) | Sort primitives / sort objects with a Comparator | O(n log n) |
Arrays.asList(T...) | Fixed-size List view over an object array | O(1) |
System.arraycopy(src, i, dst, j, len) | Fast block copy between arrays | O(len) |
s.charAt(i) / s.toCharArray() | One char / a mutable copy of all chars | O(1) / O(n) |
sb.append(x) / sb.reverse() / sb.toString() | Build, reverse and finish a string | O(1) amortised / O(n) / O(n) |
String.valueOf(x) / Integer.parseInt(s) / Long.parseLong(s) | Number ↔ text | O(digits) |
s.split(regex) / s.trim() / s.strip() | Cut into pieces / drop spaces at both ends | O(n) |
s.equals(t) / s.compareTo(t) / s.equalsIgnoreCase(t) | Same text? / which comes first? | O(n) |
Character.isDigit / isLetter / toLowerCase | Classify or normalise one char | O(1) |
A fixed number of numeric slots (counts, DP, flags)
→ int[] / boolean[]
Compact, zero-filled, O(1) by index, no boxing.
Size unknown, or the method returns a List
→ ArrayList<Integer>
Grows by itself; pass n as the capacity when you know it.
Sort numbers in a custom order
→ Integer[] + Comparator (or sort ascending, read backwards)
Arrays.sort(int[]) takes no Comparator.
Build text in a loop
→ StringBuilder
O(n) total instead of O(n²).
Edit characters in place
→ char[] cs = s.toCharArray() … new String(cs)
A String can't change; a char array can.
Count lowercase letters
→ int[26] with c - 'a'
O(1) per char and 26 counters — lighter than a HashMap.
07 · recognise
Spot the pattern
"Only lowercase English letters"
int[26] with c - 'a' — no HashMap needed
Values in a small range, like 1 ≤ nums[i] ≤ 100
A counting array indexed by the value: new int[101]
Return a string you build piece by piece
StringBuilder, then one toString()
"Words separated by one or more spaces"
trim() + split("\\s+"), or scan with an index
m × n grid, matrix, int[][]
grid.length rows, grid[r].length columns; r outer, c inner
Return type List<Integer> / List<Boolean>
new ArrayList<>(n) when n is known; values get boxed
Output size known up front (2n, n + 1, m·n)
Allocate new int[size] once and fill it by index arithmetic
08 · traps & tricks
Mistakes to avoid, tricks to use
traps
arr.length(),s.length,list.length— three wrong spellings. The compiler catches them, but under pressure they cost minutes.==on arrays or strings compares references.Arrays.equals(a, b)anda.equals(b)compare contents.System.out.println(arr)prints[I@…. UseArrays.toString(arr); for 2D,Arrays.deepToString(grid).Arrays.asList(intArray)is aList<int[]>with one element. AndArrays.asList/List.ofcan't grow — wrap them innew ArrayList<>(…).s.trim();ors.toUpperCase();on its own line does nothing — keep the returned string.split(".")andsplit("|")are regexes: escape them. And"a b".split(" ")contains an empty token.'a' + 1is the int 98, and"" + 'a' + 1is "a1". Cast when you want a char:(char) ('a' + 1).- Unboxing
nullthrows:int x = map.get(missingKey)or reading an unfilledInteger[]. grid.clone()on a 2D array shares the rows. Clone each row.Integer.parseInt(" 42")throws — trim first. Values above 2,147,483,647 needLong.parseLong.
tricks
- Before coding, write the sizes down:
n = nums.length,m = grid.length,k = s.length(). Fewer typos, clearer loops. - Debug arrays with one line:
System.out.println(Arrays.toString(a));. - When the output size is known, allocate once (
new int[2 * n]) instead of growing a list. - Mixed case?
Character.toLowerCase(c)first, thenc - 'a'. - Say library costs out loud in interviews: "
substringcopies, so this line is O(n)." Interviewers notice. - Chalo, one habit for life: the moment you write
+=on a String inside a loop, switch toStringBuilder.
09 · why & how
Questions you might be too shy to ask
Why is it length for arrays but length() for strings?
An array's length is a final field fixed when the array is created, so there's nothing to compute. String is a class and length() is one of its methods; collections use size(). Everyone mixes these up at first, and the compiler tells you at once. Learn the three and move on.
Why does my loop crash with i <= arr.length?
Indexes start at 0, so an array of length 3 has slots 0, 1, 2. The last valid index is arr.length - 1. With <=, the last round reads arr[3] and throws ArrayIndexOutOfBoundsException. Default habit: for (int i = 0; i < n; i++).
Should I use a for-each loop or an index loop?
For-each (for (int x : nums)) is perfect when you only read the values. Use an index loop when you need the position, a neighbour like nums[i + 1], a backward walk, or a write into the array. Dhyan do: x = 0 inside a for-each changes only the copy x, never the array.
int[], Integer[] or List<Integer> — which one?
Default to int[] when the size is known (counts, DP tables, prefix sums). Use List<Integer> when the size grows or the method must return a List. Integer[] is mainly for sorting with a Comparator. Converting costs O(n), so choose early.
I called list.remove(1) on a List<Integer> and the wrong thing disappeared. Why?
remove has two versions. remove(int index) removes by position; remove(Object o) removes by value. A plain 1 is an int, so Java picks the index version. For [10, 20, 1], list.remove(1) gives [10, 1], while list.remove(Integer.valueOf(1)) gives [10, 20].
The method must return int[], but I collected my answer in a List<Integer>. How do I convert?
Allocate and copy: int[] ans = new int[list.size()]; then ans[i] = list.get(i) in a loop. The one-liner list.stream().mapToInt(Integer::intValue).toArray() does the same O(n) work. If you knew the size up front, skip the list and fill an int[] directly.
Why doesn't Java make s += c fast for me?
Because String is immutable: s += c must leave the old string untouched, so it builds a new one and copies everything. javac compiles each += on its own; it doesn't turn your whole loop into one builder. A single a + b + c is fine — the trap is repeating += n times.
My answer is built from the back. Should I call sb.insert(0, c) every time?
No. insert(0, c) shifts every char already in the builder one place right, so n inserts cost O(n²) — the same trap as +=. Instead append in the order you get the chars, then call sb.reverse() once at the end: O(n) total.
Is substring O(1)?
Not since Java 7 update 6 (2012). Before that, a substring shared the original's character array; OpenJDK removed that sharing (bug JDK-6924259), so today substring(a, b) copies b − a chars. Treat it as O(length).
Why does "1.2.3".split(".") return an empty array?
split takes a regular expression, and . matches any character. Every char becomes a separator, every piece is empty, and trailing empty pieces are dropped — nothing is left. Write split("\\.").
Everyone says never use ==. Is s.charAt(i) == 'a' wrong too?
No, that one is correct. A char is a primitive (just a number), so == compares the values directly. The == trap is only for objects: String, Integer, arrays. Simple hai: char → ==, String → equals.
s.charAt(i) or s.toCharArray() — which should I use?
To only read characters, charAt(i) is enough: O(1) each and no copy, so O(1) extra space. toCharArray() copies the whole string once (O(n) time and space). Take that copy when you must change chars: sort them, swap them, or overwrite them.
Why is an int[26] counter called O(1) space? It is still an array.
Space complexity asks how memory grows with the input size n. The 26 counters stay 26 whether s has 5 chars or 10⁵ chars, so the growth is zero: O(1). Some interviewers write O(k) for an alphabet of size k. Say both, and you are safe.
Is this the arrays chapter? Where are Kadane and prefix sums?
This chapter is only the Java mechanics. Why indexing is O(1), shifting, dynamic arrays, Kadane and prefix tricks live in Arrays & Strings (Stage 3), after you have Big-O. Bas itna yaad rakho: here you learn the tools, there you learn the patterns.
10 · practice
Interview problems
Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.
- #58Length of Last WordEasyBloombergGoogleAmazonMicrosoft
- #1929Concatenation of ArrayEasyGoogleBloombergAmazonInfosys
- #1672Richest Customer WealthEasyGoogleMicrosoftMeta
- #1431Kids With the Greatest Number of CandiesEasyAmazonMetaMicrosoft
- #1470Shuffle the ArrayEasyMicrosoftGoogleAmazon
- #387
- #1512Number of Good PairsEasyBloombergGoogleMicrosoft
Also practise this here
Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.
- #242Valid AnagramEasyin Arrays & Strings
The same
int[26]+c - 'a'mechanics, used to compare two strings. - #125Valid PalindromeEasyin Two Pointers
charAt,Character.isLetterOrDigitandtoLowerCaseinside a two-pointer scan. - #1480
11 · revise
The whole chapter on one screen
8 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.
- what it is
int[]= fixed row of slots, pre-filled with 0/false/null.String= text you read but never change.StringBuilder= text you build. - use it when
Size known →
int[]. Size unknown or a List is returned →ArrayList<Integer>. Text in a loop →StringBuilder. Only a–z →int[26]. - template
int[] count = new int[26]; for (char c : s.toCharArray()) count[c - 'a']++; - cost
s += cn times copies 1 + 2 + … + n chars: O(n²).sb.append(c)is O(1) amortised: O(n).substring,split,toCharArraycopy: O(n). - trap
arr.length·s.length()·list.size(). And==on arrays or strings compares references — useArrays.equals/equals. - trap
Arrays.asList(int[])is aList<int[]>of ONE element.split(".")splits on every char — escape it:split("\\."). - pro tip
Print with
Arrays.toString(a)(2D:deepToString). Plainprintln(a)shows junk like[I@1b6d3586. - pro tip
'a' + 1is the int 98;(char) ('a' + 1)is 'b'. Digit chars:c - '0'.
12 · recall
Flip, remember, repeat
Array, String, List: how do you get the size of each?
Question 1: Array, String, List: how do you get the size of each?
13 · check yourself
Quick quiz
For `int[] a` and `String s`, which line compiles?
What does `new String[3]` contain?
You build a 10⁵-char string one char at a time. Which way is O(n)?
`List<Integer> list = Arrays.asList(1, 2, 3); list.add(4);` — what happens?
`char c = 'd';` What is `c - 'a'`?
`"a,b,,".split(",").length` is…
`String a = "upi"; String b = new String("upi");` Which is true?
14 · learn more
A second teacher, if you want one
- ▶ videoIntroduction to Arrays and ArrayList in Java ↗Kunal Kushwaha · EnglishArrays and ArrayList from zero: memory, 2D arrays, how a dynamic array grows, and arrays passed into functions, with questions solved along the way.
- ▶ videoStrings and StringBuilder in Java ↗Kunal Kushwaha · EnglishHow Java strings work inside: the pool, immutability and memory. Once you see why `+=` in a loop is slow, StringBuilder makes sense.
- ▶ videoLecture 14: Strings in Java || DSA Series by Babbar ↗Love Babbar (CodeHelp) · HindiJava strings in Hindi, from a Java series built for DSA, with written notes linked. Watch this if Kunal's pace feels fast.