Insights into Company
← Java

Arrays and Strings

Understand the idea. Compare it. Explain it. Test yourself.

Question 1 of 7

How are arrays created and initialized?

Solution and explanation

What an array is

An array is a fixed-size, ordered container of elements of one type, stored in contiguous memory. Arrays are objects in Java: they live on the heap and have a length field. Indexing starts at 0, and access by index is O(1).

Creating arrays

int[] marks = new int[5];                 // {0, 0, 0, 0, 0} default values
String[] cities = new String[3];          // {null, null, null}
int[] primes = {2, 3, 5, 7, 11};          // declare + initialise
double[] salaries = new double[]{45000.0, 52000.5};

Default values: 0 for numbers, false for boolean, '\u0000' for char, null for objects.

Reading and looping

System.out.println(primes[0]);            // 2
System.out.println(primes.length);        // 5 (a field, not a method)

for (int i = 0; i < primes.length; i++) System.out.print(primes[i] + " ");
for (int p : primes) System.out.print(p + " ");           // enhanced for

primes[5] throws ArrayIndexOutOfBoundsException.

Useful java.util.Arrays methods

int[] a = {5, 2, 9, 1};
Arrays.sort(a);                            // [1, 2, 5, 9]  dual-pivot quicksort for primitives
int pos = Arrays.binarySearch(a, 5);       // 2 (array must be sorted)
int[] copy = Arrays.copyOf(a, 6);          // [1, 2, 5, 9, 0, 0]
Arrays.fill(a, -1);                        // all -1
System.out.println(Arrays.toString(copy)); // printing an array directly shows [I@1b6d3586
boolean same = Arrays.equals(a, copy);

Arrays are references

int[] x = {1, 2, 3};
int[] y = x;      // same array
y[0] = 99;        // x[0] is also 99
int[] z = x.clone();  // independent copy (shallow for object arrays)

Reading an array from input

Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) arr[i] = sc.nextInt();

Summary

Arrays have fixed size, O(1) index access, default values and a length field; use java.util.Arrays for sorting, searching, copying and printing.

Practice explaining this
Question 2 of 7

What is the difference between an array and ArrayList?

Solution and explanation

Comparison

ArrayArrayList
Sizefixed when createdgrows automatically
Element typesprimitives and objectsobjects only (Integer, not int; autoboxing)
Lengtharr.lengthlist.size()
Accessarr[i]list.get(i)
Add/removenot possible (create a new array)add, remove, addAll
Generics / type safetybuilt inArrayList<String>
Utility methodsArrays.*Collections.*, streams, contains, indexOf
Performanceslightly faster, less memorysmall overhead, boxing for numbers

How ArrayList grows

Internally an ArrayList holds an Object[]. When it is full, it creates a new array about **1.5 times** larger and copies the elements. So add is **amortised O(1)**; inserting or removing in the middle is O(n) because elements shift.

List<String> skills = new ArrayList<>();
skills.add("Java");
skills.add("Spring Boot");
skills.add(0, "SQL");          // shifts others right
skills.remove("Java");
System.out.println(skills);    // [SQL, Spring Boot]

List<Integer> big = new ArrayList<>(10_000);   // pre-size to avoid repeated resizing

Converting

String[] arr = {"a", "b"};
List<String> fixed = Arrays.asList(arr);          // fixed-size view: add() throws
List<String> list  = new ArrayList<>(Arrays.asList(arr));   // modifiable
List<String> immutable = List.of("a", "b");       // Java 9+, unmodifiable
String[] back = list.toArray(new String[0]);
int[] nums = list2.stream().mapToInt(Integer::intValue).toArray();

Common pitfall: removing while looping

for (String s : skills) if (s.isBlank()) skills.remove(s);   // ConcurrentModificationException
skills.removeIf(String::isBlank);                             // correct

When to use which

  • Array: fixed size known in advance, primitives, performance-critical code (DSA problems).
  • ArrayList: everyday business code where the number of items changes.

Summary

Arrays are fixed-size and support primitives; ArrayList is a resizable, generic list backed by an array, with amortised O(1) appends and O(n) middle inserts.

Practice explaining this
Question 3 of 7

How do multidimensional arrays work?

Solution and explanation

Concept

Java has no true matrix type: a 2D array is an **array of arrays**. Each row is a separate array object, so rows can even have different lengths (jagged arrays).

int[][] grid = new int[3][4];          // 3 rows, 4 columns, all 0
grid[1][2] = 7;
System.out.println(grid.length);       // 3 (rows)
System.out.println(grid[0].length);    // 4 (columns)

int[][] marks = {
    {78, 85, 90},
    {66, 72, 81},
};

Looping

for (int r = 0; r < marks.length; r++) {
    for (int c = 0; c < marks[r].length; c++) {
        System.out.print(marks[r][c] + " ");
    }
    System.out.println();
}
for (int[] row : marks) System.out.println(Arrays.toString(row));
System.out.println(Arrays.deepToString(marks));    // [[78, 85, 90], [66, 72, 81]]

Jagged arrays

int[][] triangle = new int[4][];
for (int i = 0; i < 4; i++) {
    triangle[i] = new int[i + 1];
    triangle[i][0] = triangle[i][i] = 1;
    for (int j = 1; j < i; j++) triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
}
// Pascal's triangle: [1] [1,1] [1,2,1] [1,3,3,1]

Example: matrix multiplication

static int[][] multiply(int[][] a, int[][] b) {
    int n = a.length, m = b[0].length, k = b.length;
    int[][] c = new int[n][m];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            for (int x = 0; x < k; x++)
                c[i][j] += a[i][x] * b[x][j];
    return c;
}

Time complexity O(n·m·k). Accessing row by row (c[i][j] with j in the inner loop) is cache-friendly because each row is contiguous.

Copying

grid.clone() copies only the outer array; rows are still shared. Deep copy:

int[][] copy = new int[grid.length][];
for (int i = 0; i < grid.length; i++) copy[i] = grid[i].clone();

Summary

A 2D array is an array of row arrays; use arr.length for rows and arr[i].length for columns, Arrays.deepToString for printing, and copy each row for a deep copy.

Practice explaining this
Question 4 of 7

Why is String immutable?

Solution and explanation

What immutable means

Once a String object is created its characters can never change. Methods that look like they modify it (toUpperCase, replace, concat, substring) return a **new** String.

String s = "java";
s.toUpperCase();            // result discarded
System.out.println(s);      // java
s = s.toUpperCase();        // s now points to a new object "JAVA"

The class is final, its internal byte[] value is private final, and no method changes it.

Why Java designed it this way

  1. **String pool:** literals are shared. String a = "hi"; String b = "hi"; point to one object. If strings were mutable, changing a would silently change b.
  2. **Security:** strings hold file paths, URLs, database usernames and class names. After validation (if (path.startsWith("/safe"))), nobody can change the value before it is used.
  3. **Thread safety:** immutable objects can be shared between threads without synchronisation.
  4. **Hash caching:** hashCode() is computed once and cached, which makes String an efficient and reliable HashMap key. A key whose hash changed after insertion could never be found again.
  5. **Class loading:** class names passed as strings cannot be altered.

Cost: creating many objects

String result = "";
for (int i = 0; i < 10000; i++) result += i;   // creates ~10,000 temporary Strings: O(n^2)

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i); // one growing buffer: O(n)
String result2 = sb.toString();

(The compiler optimises a + b + c in a single expression, but not concatenation inside loops.)

Passwords

Because a String stays in memory until garbage-collected and cannot be wiped, APIs use char[] for passwords (e.g. JPasswordField.getPassword()), which can be overwritten with zeros after use.

Summary

String is immutable for pooling, security, thread safety and hash caching; use StringBuilder when building strings in loops.

Practice explaining this
Question 5 of 7

What is the String pool?

Solution and explanation

What it is

The String pool (string constant pool, or intern pool) is a special area in the heap where Java keeps **one shared copy of each distinct string literal**. Since Java 7 it lives in the main heap (earlier in PermGen), so it is garbage-collected like other objects.

String a = "Pune";
String b = "Pune";
String c = new String("Pune");

System.out.println(a == b);        // true: same pooled object
System.out.println(a == c);        // false: new String creates a separate heap object
System.out.println(a.equals(c));   // true: same characters
System.out.println(a == c.intern()); // true: intern() returns the pooled copy

How it works

  • When the class loads, literals like "Pune" are added to the pool if not already there.
  • new String("Pune") always creates a new object on the heap (and the literal "Pune" is in the pool too), so up to **two objects** may exist.
  • intern() looks up the pool: returns the existing pooled string, or adds this one.
  • Compile-time constants are pooled: "Pu" + "ne" == "Pune" is true; but s1 + s2 with variables at runtime creates a new object (unless both are final constants).

Why it exists

Programs repeat the same strings (status codes, city names, JSON keys). Sharing one copy saves memory, and immutability makes sharing safe.

Interview takeaways

  • Always compare strings with equals() (or equalsIgnoreCase()), never ==. == compares references and only works by accident for pooled literals.
  • "constant".equals(variable) avoids NullPointerException when the variable is null; or use Objects.equals(a, b).
  • Interning large numbers of runtime strings can save memory (e.g. repeated values parsed from a big file), but the JVM's G1 GC also offers -XX:+UseStringDeduplication.

Summary

The String pool stores one shared instance per literal; new String bypasses it, intern() uses it, and equals() is the correct way to compare contents.

Practice explaining this
Question 6 of 7

What is the difference between String, StringBuilder and StringBuffer?

Solution and explanation

Comparison

StringStringBuilderStringBuffer
Mutablenoyesyes
Thread-safeyes (immutable)noyes (synchronized methods)
Speed for repeated changesslow (new object each time)fastestslower than StringBuilder
IntroducedJava 1.0Java 5Java 1.0
Use forfixed text, keys, constantsbuilding text in one threadlegacy code shared across threads

StringBuilder in action

StringBuilder sb = new StringBuilder("Java");
sb.append(" Developer")          // Java Developer
  .insert(0, "Senior ")          // Senior Java Developer
  .replace(0, 6, "Lead")         // Lead Java Developer
  .reverse();                    // repoleveD avaJ daeL
sb.reverse();
sb.setCharAt(0, 'l');
sb.deleteCharAt(sb.length() - 1);
System.out.println(sb.length() + " " + sb);
String result = sb.toString();

Internally it keeps a resizable character buffer (default capacity 16, grows to (old * 2) + 2). Give a capacity if you know the size: new StringBuilder(1000).

Performance example

// Building a CSV of 1 lakh rows
StringBuilder csv = new StringBuilder();
for (Job j : jobs) {
    csv.append(j.getId()).append(',').append(j.getTitle()).append('\n');
}

Using String += here would copy the entire growing text on each iteration (O(n^2)).

StringBuffer today

Its methods are synchronized, so each call takes a lock even in single-threaded code. Sharing one mutable buffer between threads is rare; usually each thread builds its own StringBuilder. Use StringBuffer only when an old API requires it.

Other options

  • String.join(", ", list) and Collectors.joining(", ") for joining.
  • String.format / "%s".formatted(x) for templates.
  • Text blocks (""", Java 15) for multi-line literals.

Summary

Use String for values that do not change, StringBuilder for building or modifying text (almost always), and StringBuffer only when a mutable buffer really must be shared between threads.

Practice explaining this
Question 7 of 7

How do equals() and compareTo() differ?

Solution and explanation

equals()

Answers **"are these two objects equal in content?"** and returns boolean. It is defined in Object (default: same reference, like ==) and overridden by String, wrappers, collections and your own classes.

"Java".equals("Java");             // true
"Java".equals("java");             // false
"Java".equalsIgnoreCase("java");   // true

compareTo()

Answers **"how do they order?"** and returns an int: negative if this comes before the other, 0 if equal in ordering, positive if after. It comes from the Comparable<T> interface and defines the **natural order** used by Collections.sort, TreeMap and TreeSet.

"apple".compareTo("banana");   // negative ('a' < 'b')
"b".compareTo("a");            // positive
"Java".compareTo("Java");      // 0
"Java".compareTo("java");      // negative: 'J' (74) < 'j' (106)
"Java".compareToIgnoreCase("java");   // 0

For strings, it compares character by character (Unicode values); if one string is a prefix of the other, the shorter one is smaller ("app".compareTo("apple") = -2, the length difference).

Consistency between them

It is strongly recommended that a.compareTo(b) == 0 exactly when a.equals(b). BigDecimal is a known exception: new BigDecimal("2.0").equals(new BigDecimal("2.00")) is false (different scale) but compareTo returns 0. In a HashSet both values are kept; in a TreeSet only one.

Implementing both in your class

public class Employee implements Comparable<Employee> {
    private final String empId;
    private final double salary;

    @Override public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Employee e)) return false;
        return empId.equals(e.empId);
    }
    @Override public int hashCode() { return empId.hashCode(); }   // always override with equals

    @Override public int compareTo(Employee other) {
        return Double.compare(this.salary, other.salary);          // never subtract doubles
    }
}

Summary

equals() checks equality and returns boolean; compareTo() defines ordering and returns negative, zero or positive. Keep them consistent, and always override hashCode() with equals().

Practice explaining this