Topics › Basics › Things to Know in C++/Java/Python or any language
What are arrays, strings?
Java source ↗ Edit this article ↗
Jump to code:
What this covers
Arrays and strings are the two data structures that every other topic on these sheets is built on. An array is a fixed-size block of slots of one type, addressed by an index from 0 to length - 1. A String is an immutable sequence of characters with a rich set of methods. This article explains how each is created and accessed, the reference semantics that make arrays behave differently from numbers when passed around, two-dimensional arrays, the immutability of strings and what it means for performance, and the char-arithmetic trick that turns letters into array indices.
Creating and indexing an array
new int[n] allocates n slots, all initialised to 0 (false for boolean[], null for object arrays). Reading or writing uses a[i], and a.length (a field, not a method) gives the size. Indices run from 0 to n - 1; anything else throws ArrayIndexOutOfBoundsException.
static int[] squares(int n) {
int[] a = new int[n];
for (int i = 0; i < n; i++) a[i] = i * i;
return a;
}
For n = 0 the array is empty and the loop runs zero times, which is a legitimate value, not an error. When the index itself is not needed, the enhanced for visits each element in order:
static long sum(int[] a) {
long total = 0;
for (int x : a) total += x;
return total;
}
The accumulator is a long because 10^5 values of 10^4 each already exceed what an int holds. The enhanced loop gives you a copy of each element in x; assigning to x does not change the array, so use an index loop when you need to write.
Arrays are references
An array variable does not hold the elements; it holds a reference to the array object. Passing the array to a method copies that reference, so the method works on the same elements and any change it makes is visible to the caller. That is what makes in-place algorithms possible:
static void reverseInPlace(int[] a) {
int i = 0, j = a.length - 1;
while (i < j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
i++;
j--;
}
}
The method returns nothing, yet after the call the caller’s array is reversed. The same fact has a flip side. Writing int[] alias = a; does not copy the elements; it creates a second name for the same array. To get an independent copy, use Arrays.copyOf:
static boolean[] aliasVersusCopy(int[] a) {
int[] alias = a;
int[] copy = Arrays.copyOf(a, a.length);
alias[0] = 99;
return new boolean[]{a[0] == 99, copy[0] == 99};
}
The result is always {true, false}: writing through alias changed a, while copy kept the old value. Comparing arrays has the same trap: a == b asks whether they are the same object; Arrays.equals(a, b) asks whether they hold the same elements. Arrays.toString(a) prints the contents, whereas System.out.println(a) prints something like [I@1b6d3586.
Two-dimensional arrays
new int[rows][cols] creates an array of rows references, each pointing at a row array of cols ints. grid[r][c] is column c of row r, and grid.length is the number of rows while grid[r].length is the width of that row.
static int[][] identity(int n) {
int[][] grid = new int[n][n];
for (int i = 0; i < n; i++) grid[i][i] = 1;
return grid;
}
Because every row is its own array, rows may have different lengths (a “jagged” array), and a row may even be empty. Code that loops to a fixed width breaks on such input; looping to grid[r].length does not:
static int[] rowSums(int[][] grid) {
int[] sums = new int[grid.length];
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) sums[r] += grid[r][c];
}
return sums;
}
To compare 2D arrays by content use Arrays.deepEquals; plain Arrays.equals would compare the row references.
Strings: a sequence of chars
A String is a sequence of char values. s.length() (a method this time) gives the count, s.charAt(i) returns the character at index i, and the same 0 to length - 1 rule applies. The usual loop looks exactly like an array loop:
static int countVowels(String s) {
int count = 0;
for (int i = 0; i < s.length(); i++) {
char c = Character.toLowerCase(s.charAt(i));
if ("aeiou".indexOf(c) >= 0) count++;
}
return count;
}
"aeiou".indexOf(c) returns the position of c in that string, or -1 when absent, which is a compact membership test for a handful of characters. Other everyday methods: substring(from, to) (end exclusive), indexOf, contains, startsWith, split, trim, toUpperCase, and equals for comparison.
Strings are immutable
No method on String changes the string it is called on. Every “modifying” method returns a new string, and the original is untouched:
static String[] concatDoesNotMutate(String s) {
String t = s.concat("!");
return new String[]{s, t};
}
concatDoesNotMutate("hi") returns {"hi", "hi!"}. Calling s.concat("!") and ignoring the result does nothing at all, which is a frequent beginner bug. Immutability also means a string cannot be reversed in place; the standard approach is to copy it into a char[], which is mutable, work there, and build a new string at the end:
static String reverse(String s) {
char[] c = s.toCharArray();
int i = 0, j = c.length - 1;
while (i < j) {
char t = c[i];
c[i] = c[j];
c[j] = t;
i++;
j--;
}
return new String(c);
}
Compare this with reverseInPlace above: the loop is identical, only the container changed.
Letters as array indices
Because a char is a number ('a' is 97), c - 'a' maps the lowercase letters to 0 through 25. A 26-slot array indexed this way is the fastest possible frequency table, and it appears in anagram checks, first-unique-character problems and many string hashing solutions:
static int[] letterFrequency(String s) {
int[] freq = new int[26];
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 'a' && c <= 'z') freq[c - 'a']++;
}
return freq;
}
The range check matters: without it an uppercase letter or a space would produce a negative or oversized index and throw. For arbitrary characters use a HashMap<Character, Integer> or a int[128] table indexed by the char itself.
Building strings efficiently
Since each + on strings creates a new object and copies all characters so far, building a long string with result += piece in a loop costs O(n^2) characters copied. StringBuilder keeps a growable buffer and appends in amortised constant time; convert to a String once at the end.
static String joinReversedWords(String sentence) {
String[] words = sentence.trim().split("\\s+");
StringBuilder sb = new StringBuilder();
for (int i = words.length - 1; i >= 0; i--) {
if (sb.length() > 0) sb.append(' ');
sb.append(words[i]);
}
return sb.toString();
}
The sb.length() > 0 guard adds a space before every word except the first, so there is no trailing space to trim afterwards. split("\\s+") collapses runs of blanks, and trim() prevents an empty first piece when the sentence starts with spaces.
Edge cases and pitfalls
- Index range. Valid indices are
0tolength - 1. Loops written asi <= a.lengththrow on the last iteration. - Empty inputs.
new int[0]and""are valid; make sure loops anda[0]accesses tolerate them, or state that the input is non-empty. - Reference versus copy.
b = aaliases;Arrays.copyOfcopies. Changes through an alias are visible everywhere the array is referenced. ==on strings and arrays. Compares references. Useequalsfor strings,Arrays.equalsandArrays.deepEqualsfor arrays.- Ignored results.
s.toUpperCase();on its own does nothing; assign the result. - Char arithmetic range. Guard
c - 'a'with a range check unless the input is guaranteed lowercase. - Quadratic concatenation. Use
StringBuilderin loops. - Overflow in sums. Accumulate array sums in
long.
Related problems
- For loops covers the index loops used throughout this article.
- While loops shows the two-pointer loop shape used in
reverseon other problems. - Two Sum is the first classic array problem, from brute force to hashing.
- Java Collections introduces
ArrayList, the growable alternative to a fixed array.