Room 14 of 14 · about 20 minutes

Parallel arrays, and the class that replaces them

You now have every array tool in the unit. This room is where using them is the wrong answer, and what to reach for instead.

Tasks checked0 of 3 XP earned on this path0

What this room checks you can do

Student can recognize a parallel-arrays design in code (multiple arrays indexed in lockstep, with the relationship encoded only by index convention), name the four characteristic costs (sync drift, no type guarantee, refactor cost, poor readability), and identify the CSCD 210 contexts where it is intentionally accepted (weeks 6–7) versus rejected (week 8+).

Notes

Parallel arrays: the temptation

A parallel-arrays design uses two or more arrays that share an index space: names[i] is the name of the student whose grade is grades[i] and whose attendance is attended[i]. The arrays are "parallel" because they are kept in sync by the convention that the same index refers to the same logical entity.

final String[] names    = {"Ada", "Bob",  "Cay"};
final int[]    grades   = { 95,    78,    88   };
final boolean[] attended = { true,  false, true };

//  Access "row 1": Bob, 78, did not attend
//  print every student's record:
for (int i = 0; i < names.length; i++) {
    System.out.println(names[i] + " " + grades[i] + " " + attended[i]);
}

The temptation is real. Students reach for parallel arrays when the lab introduces a new "shape" of data before they have seen objects: the first place this appears in CSCD 210 is Lab 10 (Wordle), where students sometimes try to track each letter's position and color separately rather than encapsulate them.

Why it is a recognized anti-pattern

Parallel arrays are technically correct (the code runs, the output is right), but they buy a class of bugs that the proper alternative, a class with fields, does not carry:

1. Sync drift. Every operation that adds or removes an entry must touch every parallel array consistently. Forgetting to update one of the arrays produces silent corruption: names[3] and grades[3] describe different students. 2. No type-level guarantee. A function that receives all three arrays as parameters has no way to verify they have the same length, that the indices align, that names[2] and grades[2] belong together. The relationship lives only in the programmer's head. 3. Refactor cost. Adding a fourth attribute means changing every method signature in the codebase. A class with four fields adds one field; no other signatures change. 4. Reads poorly. names[i] + " " + grades[i] + " " + attended[i] repeats the index three times. The reader has to verify that all three are the same i. A Student class would let the same line read s.toString().

When CSCD 210 lab style allows it

Week 6 of CSCD 210 introduces parallel arrays before week 8 introduces classes, so during weeks 6 and 7, the parallel-arrays form is the only tool students have for "data with multiple attributes per entry." The CSCD 210 convention is:

  • Use parallel arrays in Lab 10 (Wordle position + color tracking) as a deliberate pre-classes exercise.
  • Refactor to a class with fields in Lab 11 (Typed File Stats) or Lab 12, once classes are available.
  • In production code (or CSCD 211+), never use parallel arrays. The class form is always available there.

In other languages

  • C: parallel arrays are common because C's struct is a heavyweight choice for small ad-hoc bundles. The trade-off is sharper in C.
  • Python: parallel arrays are sometimes seen but are widely considered an anti-pattern; the analog is zip(names, grades, attended) for iteration, paired with namedtuple or dataclass for the encapsulation.
  • R: data frames (a built-in tabular type) avoid the parallel-arrays question entirely.

Prefer a class with fields

The corrective pattern for parallel arrays: define a class whose fields are the attributes that were spread across the parallel arrays, then use a single T[] (or ArrayList<T>) of that class. The relationship between attributes ("these belong to the same student") moves from "the programmer remembers" to "the language enforces."

//  Before: parallel arrays
final String[] names    = {"Ada", "Bob", "Cay"};
final int[]    grades   = { 95,   78,    88  };
final boolean[] attended = { true, false, true };

//  After: a class with fields, plus a single array
public class Student {
    public final String  name;
    public final int     grade;
    public final boolean attended;

    public Student(final String name, final int grade, final boolean attended) {
        this.name     = name;
        this.grade    = grade;
        this.attended = attended;
    }
}

final Student[] roster = {
    new Student("Ada", 95, true),
    new Student("Bob", 78, false),
    new Student("Cay", 88, true)
};

Three things change for the better:

1. Sync drift becomes impossible. Each Student is allocated, populated, and read as one object. Adding a new student is new Student(...); the language guarantees all three fields are populated. Removing a student removes one slot; there is no second array to forget. 2. Method signatures shrink. What was void printRoster(String[], int[], boolean[]) becomes void printRoster(Student[]). The signature now expresses what is passed (a roster) rather than how the data is organized internally. Adding a fourth attribute (email) adds one field to the class and zero parameters to existing methods. 3. Sorting works. Arrays.sort(roster, (a, b) -> a.grade - b.grade); sorts the roster by grade while keeping each Student's fields aligned. Comparator syntax is CSCD 211 territory; CSCD 210 students see Arrays.sort(roster) with a Comparable<Student> implementation, but the principle is the same: one sort touches one array; alignment is preserved.

Forward reference to classes and objects

This material names the pattern, and classes and objects authors it in detail. Specifically:

  • declaring public final fields as the state of a class.
  • writing the constructor that takes the field values.
  • the getName(), getGrade() accessor style, for when direct field access is not appropriate.

CSCD 210's week-8 lab introduces these explicitly. Lab 11 is the first lab that requires a class with fields rather than parallel arrays: the "refactor" trajectory is built into the curriculum.

When the class is "overkill"

The objection "a class is overkill for three pieces of data" is real but usually wrong. The class costs:

  • A class declaration (8–10 lines).
  • A constructor (2–4 lines).
  • A toString() (3–5 lines).

…for a total of ~20 lines. The parallel-arrays equivalent costs:

  • Three array declarations (3 lines).
  • A printRoster method that takes all three (5–10 lines).
  • Helper methods to add/remove/find by name (15–20 lines of synchronized triple-update logic).

The class form is usually shorter once the operations are written. The "overkill" intuition assumes the data will only be read, never modified, which is rare.

Java 16+ shrinks the boilerplate: record

Java 16 (2021) added records, a compact form for immutable data classes:

public record Student(String name, int grade, boolean attended) { }

That single line generates the constructor, the three accessor methods (name(), grade(), attended()), toString, equals, and hashCode. For a CSCD 210 student-roster use case, the record form replaces the 10-line class declaration above. CSCD 211 teaches records explicitly; CSCD 210 weeks 8–9 use the longer public final field form to make the constructor body visible. The trade-off the longer form pays for is seeing the field assignments; once that mechanism is internalized, records are the production choice.

In other languages

  • C: struct Student { char* name; int grade; bool attended; }; plus Student roster[3]. Same shape.
  • Python: @dataclass class Student: name: str; grade: int; attended: bool plus roster: list[Student]. The @dataclass decorator removes much of Java's boilerplate.
  • Rust: struct Student { name: String, grade: i32, attended: bool } plus Vec<Student>. Same.
  • TypeScript: interface Student { name: string; grade: number; attended: boolean } plus Student[]. Same.

Every modern language has the class-with-fields pattern. Java's verbose declaration syntax (constructor explicit, no @dataclass equivalent until record in Java 16) makes the pattern feel heavier than it is, but the trade-off remains in Java's favor.

What this room assumes you already have

Tasks

Do each one, then check the box. Checking a box is you saying you did it. You can uncheck a box if you check it by accident.

  1. trace
    Show the answer

    if a future operation removes index 3 from one array but not the other, the alignment between names[i] and grades[i] is broken silently.

  2. write
    Show the answer

    any pair of same-length arrays indexed identically across an operation.

  3. trace
    Show the answer

    one parameter where there were three; the relationship between fields is in the class, not in the convention.

Self check

Type what you think the answer is. Getting it wrong costs nothing and you can try as many times as you want.

Given the parallel-arrays form String[] names; int[] grades; boolean[] attended;, sketch the equivalent Student[] plus class declaration.

Practice, untimed

Open this whenever you want, before the tasks or after them. Nothing in this section is recorded and nothing here is timed.

  1. Sketch the migration path from parallel arrays to a class (without writing the class, which the refactor to a class with fields covers).write
    Show the answer

    define a class with fields matching the parallel arrays; create a single T[] (or ArrayList<T>) of that class; replace arr1[i], arr2[i], … with objs[i].field1, objs[i].field2, … or objs[i].toString().

  2. Given a Arrays.sort(roster, Comparator.comparing(s -> s.grade)) call (CSCD 211 syntax), explain why this would have been hard with parallel arrays.trace
    Show the answer

    parallel-array sort would have to swap slots across all three arrays in lockstep, manually.

  3. Refactor String[] names = {...}; int[] ages = {...}; plus a printPeople(names, ages) method into a Person class and a printPeople(Person[] people) method.write
    Show the answer

    class with two fields, constructor, single-array call site.

  4. Identify which parts of the classes and objects material author the class Student { ... } declaration.write
    Show the answer

    declaring the fields as the state of the class, plus writing the constructor that takes the field values.

How this room finishes

This room is done when all 3 tasks are checked and the self check is answered.