PrepZone Logo
PrepZone

Control Flow and Arrays

Loops, switch, and arrays — including jagged arrays and the copying method that actually works.

Why this matters

  • Off-by-one errors and forgotten break statements are the most common beginner bugs, and both are structural rather than conceptual.
  • Array copying has several approaches and most of them are shallow, which produces aliasing bugs that are hard to trace.
  • Knowing the limits of arrays is what makes the Collections Framework feel like a solution rather than extra API to memorise.
Hello.javaSource you write
Hello.classBytecode
JVMWindows / Linux / macOS
Machine codeRuns on the CPU
One compile step produces bytecode. Every operating system then runs that same bytecode through its own JVM.

Choosing a loop

  • for when you know the bounds or need the index. The three clauses keep initialisation, condition and update in one place.
  • Enhanced for when you only need each element. It is harder to get wrong, but gives you no index and no safe way to remove items.
  • while when the number of iterations depends on a condition you re-check — reading until end of input, for example.
  • do-while when the body must run at least once before the condition is first checked. Genuinely rare.
Java
int[] values = {4, 8, 15, 16, 23, 42};

for (int i = 0; i < values.length; i++) {       // index available
    System.out.println(i + ": " + values[i]);
}

for (int value : values) {                      // cleaner when index is unused
    System.out.println(value);
}

break, continue and labels

break leaves the loop entirely; continue skips to the next iteration. Both affect only the innermost loop unless you label the outer one.

Java
outer:
for (int row = 0; row < grid.length; row++) {
    for (int col = 0; col < grid[row].length; col++) {
        if (grid[row][col] == target) {
            System.out.println("Found at " + row + "," + col);
            break outer;              // leaves both loops
        }
    }
}

Labels are the honest way to exit nested loops. The alternative — a found flag checked in both conditions — is longer and easier to get wrong. Use them sparingly but without guilt.

switch and the fall-through trap

The classic switch statement continues into the next case unless stopped by break.

Java
switch (day) {
    case SATURDAY:
    case SUNDAY:
        System.out.println("Weekend");    // deliberate fall-through: both share a body
        break;
    case FRIDAY:
        System.out.println("Almost there");
        break;
    default:
        System.out.println("Weekday");
}

Grouping cases is the one good use of fall-through. Everything else is usually a forgotten break. Java 14 added arrow syntax that removes the hazard entirely, and that form is covered in the Java 17 module.

Arrays

An array is an object on the heap with a fixed length field. Creating one initialises every slot to the type's default.

Java
int[] counts = new int[5];                 // {0, 0, 0, 0, 0}
String[] names = new String[3];            // {null, null, null}
double[] rates = {1.5, 2.25, 3.0};         // literal form, length inferred

System.out.println(counts.length);         // 5 — a field, not a method

What arrays cannot do

  • Grow. The length is fixed at creation. "Adding" means allocating a bigger array and copying.
  • Hold primitives generically. int[] and Integer[] are unrelated types, and generics only work with the latter.
  • Print readably. System.out.println(array) prints a type name and hash. Use Arrays.toString or Arrays.deepToString.
  • Compare by content with ==. That compares references. Use Arrays.equals or Arrays.deepEquals.

Multi-dimensional and jagged arrays

A two-dimensional array in Java is an array of arrays, so the rows need not be the same length.

Java
int[][] grid = new int[3][4];              // rectangular: 3 rows of 4

int[][] triangle = new int[3][];           // rows allocated separately
triangle[0] = new int[] {1};
triangle[1] = new int[] {1, 2};
triangle[2] = new int[] {1, 2, 3};

for (int[] row : triangle) {
    System.out.println(Arrays.toString(row));
}
Java
[1]
[1, 2]
[1, 2, 3]

This is the key difference from languages with true rectangular arrays. grid.length is the row count; grid[0].length is that row's own length, and other rows may differ.

Copying, and which method is safe

AspectApproachBehaviour
b = aCopies the reference onlyBoth names point at one array — changes are shared
a.clone()New array, same element referencesShallow: fine for primitives, aliased for objects
Arrays.copyOf(a, n)New array of length nShallow; pads with defaults or truncates
System.arraycopy(...)Copies a range into an existing arrayShallow; fastest, most verbose
Arrays.copyOfRange(a, f, t)New array from a sliceShallow
  • b = a

    ApproachCopies the reference only
    BehaviourBoth names point at one array — changes are shared
  • a.clone()

    ApproachNew array, same element references
    BehaviourShallow: fine for primitives, aliased for objects
  • Arrays.copyOf(a, n)

    ApproachNew array of length n
    BehaviourShallow; pads with defaults or truncates
  • System.arraycopy(...)

    ApproachCopies a range into an existing array
    BehaviourShallow; fastest, most verbose
  • Arrays.copyOfRange(a, f, t)

    ApproachNew array from a slice
    BehaviourShallow

Every built-in copy is shallow. Only the array is new; object elements are still shared.

Java
int[] original = {1, 2, 3};
int[] alias = original;                      // not a copy
int[] copy = Arrays.copyOf(original, 3);     // a real copy

alias[0] = 99;
System.out.println(original[0]);             // 99 — same array
System.out.println(copy[0]);                 // 1  — independent

For an array of mutable objects, a deep copy means copying each element yourself:

Java
Point[] deep = new Point[source.length];
for (int i = 0; i < source.length; i++) {
    deep[i] = new Point(source[i].x(), source[i].y());
}

Useful Arrays methods

Java
Arrays.sort(numbers);                               // in place; dual-pivot quicksort
int index = Arrays.binarySearch(numbers, 15);        // requires a sorted array
Arrays.fill(flags, true);                            // every slot
String text = Arrays.toString(numbers);              // readable output
boolean same = Arrays.equals(first, second);         // element by element
List<String> view = Arrays.asList("a", "b");         // fixed-size view, not a real list

Common misreadings

  • "length is a method." Arrays use the field array.length. Strings and collections use methods, length() and size().
  • "clone() deep-copies." It is shallow at every level. For int[][] the rows are shared.
  • "Removing from a list inside an enhanced for loop is fine." It throws ConcurrentModificationException. Use an explicit iterator or removeIf.
  • "binarySearch works on any array." It requires sorted input, and returns a meaningless negative value otherwise.
  • "A 2D array is rectangular." It is an array of arrays, and rows can have different lengths.

Quick recall

Everything you need if you only revisit this box.

  • Indices run 0 to length - 1; i <= length is the off-by-one that throws.
  • break and continue affect the innermost loop only. Labels are the clean way out of nested loops.
  • Classic switch falls through without break. Grouping cases is the one legitimate use.
  • Arrays have a fixed length, use a length field, and need Arrays.toString to print and Arrays.equals to compare.
  • A 2D array is an array of arrays, so rows may be jagged.
  • Every built-in copy is shallow. b = a is not a copy at all; deep copying mutable elements is manual work.
  • Arrays.asList is a fixed-size view over the array — wrap it in new ArrayList<>(...) to get a modifiable list.

Test yourself

Answer these before moving on — recall is what makes it stick.