Arrays and ArrayLists
A swerve drive has four modules. A robot might have six motors, three sensors, and two controllers. The moment you find yourself naming variables motor0, motor1, motor2… you need an array. This lesson is about storing and working with collections of related values — and knowing when a fixed-size array is the right tool versus a dynamic list.
By the end of this lesson, you will:
- Declare, initialize, and access elements in a Java array using correct index syntax
- Explain zero-indexing and predict what happens when code accesses an out-of-bounds index
- Iterate over arrays with both indexed
forloops andfor-eachloops - Use
ArrayListfor collections that grow or shrink at runtime — fault logs, command queues, sensor histories - Choose correctly between an array and an
ArrayListfor a given robot problem
The Variable Naming Problem
Suppose your robot has four swerve modules. Without arrays, you might write this:
Now imagine adding a fifth module if the team changes the drive base. Or iterating over all modules to check for faults. Every operation on the group requires duplicating code for each variable. An array turns that group into a single named entity you can loop over.
Arrays: Fixed Collections of One Type
An array is an ordered collection of elements, all of the same type, stored at contiguous memory locations. Its size is fixed at creation — you cannot add or remove elements after the array is created. In FRC, arrays are the right choice whenever you have a known, constant number of hardware objects: four swerve modules, three motors in a climber gearbox, eight CAN bus devices.
Declaration and initialization
.length is a field, not a methodArray length is accessed with array.length — no parentheses. Contrast with ArrayList.size(), which is a method call and uses parentheses. Mixing these up (array.length() or list.size) is a compile error that appears often when switching between the two collection types.
Zero-Indexing: How Arrays Are Addressed
Every element in a Java array has an index — a number identifying its position. The first element is at index 0, not 1. An array of four elements has valid indices 0, 1, 2, and 3. Accessing index 4 throws ArrayIndexOutOfBoundsException.
The swerve module array below has four elements. Click any module to see how it is accessed in code and what happens at each position.
Iterating Over Arrays
The loop patterns from Lesson 5 map directly onto arrays. The right choice depends on whether you need the index during the loop body.
When you want to apply the same operation to every element and don't need to know which position you're at, for-each is cleaner and immune to off-by-one errors.
When the index carries meaning — matching a module to its corresponding desired state, building a string with position numbers, or writing to a paired array — use an indexed for loop.
The two-array pattern (modules[i] and desiredStates[i] sharing the same index) is used extensively in swerve drive code. WPILib's kinematics classes return an array of SwerveModuleState objects that are positionally matched to your module array — index 0 is always front-left, index 1 front-right, and so on. Getting the order wrong here produces a robot that drives sideways.
One of the most reliable ways to identify a swerve drive array ordering mistake at a competition is to watch the robot drive forward: if it instead spins or crabs sideways, the module array and the desired states array are mismatched by index. During inspection I've asked teams to show me how they initialize their module array and almost always that reveals whether front-left is at index 0 or somewhere else. The fix is a one-line swap in the array initializer. The diagnosis takes under a minute if you know to look there — and much longer if you don't.
Two-Dimensional Arrays
A 2D array is an array of arrays — a grid structure. In FRC, they appear in field zone grids, pre-computed lookup tables, and matrix math for odometry transformations. You access elements with two indices: grid[row][col].
| [0] | [1] | [2] | |
|---|---|---|---|
| [0] | 0 | 1 | 2 |
| [1] | 3 | 4 | 5 |
| [2] | 6 | 7 | 8 |
ArrayList: When the Size Isn't Known in Advance
An array's fixed size is a strength when you have exactly four modules — and a problem when you don't know how many elements you'll have at compile time. ArrayList<T> is Java's resizable list: elements can be added or removed at runtime, and it handles the internal memory management automatically.
ArrayList stores objects, not primitives. You can't write ArrayList<double> — you must use the wrapper class: ArrayList<Double>. Java automatically converts between double and Double (autoboxing/unboxing), so in practice you rarely notice — but it does have a small performance cost. For large numerical collections in performance-sensitive robot code, a plain double[] array is faster. For a fault log that might have zero to ten entries, ArrayList<String> is fine.
Common ArrayList methods in FRC context
Choosing Between Array and ArrayList
Both store collections of the same type. The decision comes down to one question: do you know the size at compile time, and will it stay fixed?
Fixed size, set at creation. Faster iteration. Works with primitives directly. Simpler syntax for hardware collections.
- Swerve modules (always 4)
- Drive motors (known at build)
- Sensor port assignments (fixed)
- CAN ID lookup tables
- WPILib kinematics inputs (e.g.
SwerveModuleState[])
Resizable at runtime. Requires wrapper types for primitives. Rich API (add, remove, contains). Slightly slower for tight numerical loops.
- Fault log entries (unknown count)
- Command queues for auto sequences
- Sensor history for smoothing
- Dashboard widget registrations
- Any collection that grows during a match
The most common array-vs-ArrayList mistake I see during code review is using an ArrayList for something that never changes size — like the set of swerve modules or drive motors. It works, but it forces wrapper types, adds allocation overhead, and makes the code harder to read for anyone expecting the WPILib convention of SwerveModuleState[]. The second most common is the inverse: using a fixed array for a fault log, pre-allocating 100 slots, and then writing complex index-tracking logic instead of just using an ArrayList. When in doubt: if WPILib gives you an array type in its API, match it. If you're building a collection at runtime, use ArrayList.
The Three Array Bugs That Appear Every Season
Bug 1: Accessing a null element
Creating an array with new TalonFX[4] allocates space for four references, but doesn't create any TalonFX objects. Every element starts as null. Calling a method on a null element throws NullPointerException.
Bug 2: Parallel arrays drifting out of sync
When two arrays are positionally matched — modules[i] paired with desiredStates[i] — inserting, removing, or reordering one without updating the other produces subtle, hard-to-diagnose mismatches. On a swerve drive, this makes modules respond to each other's commands.
Bug 3: Forgetting that ArrayList.remove(int) is index-based
ArrayList<Integer> has an ambiguity: remove(0) removes the element at index 0, not the element with value 0. To remove by value, cast to the wrapper type.
🔌 System Check
- Never call a method on an array element before verifying it isn't null. Creating an array only allocates the slots. Every hardware object —
TalonFX,CANcoder,SwerveModule— must be explicitly constructed and assigned to its slot inrobotInit()before any periodic method touches it. - Match WPILib array conventions exactly.
SwerveDriveKinematics,SwerveDriveOdometry, and PathPlanner all return and acceptSwerveModuleState[]in the order you defined your module array. Reordering one without the other produces a robot that drives sideways. Document your index order with named constants (FL = 0, etc.). - Use
array.length(no parentheses) andlist.size()(with parentheses). Mixing these up is a compile error when switching between the two types, and easy to do when refactoring. - For collections that grow at runtime, use
ArrayList. Pre-allocating a 50-element array with an index counter to simulate a list is harder to read, harder to maintain, and easier to break than just usingArrayList. - For performance-critical numerical loops, use primitive arrays. A
double[]with 1000 elements iterates faster thanArrayList<Double>with 1000 elements. For 4–8 hardware objects in a periodic method, the difference is negligible. For sensor history with thousands of samples, it matters. - Clear or bound
ArrayListcollections that grow during a match. A fault log or sensor history that is only ever appended to will grow without bound over a long testing session. Either cap it (if (list.size() > MAX) list.remove(0)) orclear()it between modes.
Knowledge Check
Click an answer to check your understanding.
TalonFX[] motors = new TalonFX[4]; followed immediately by motors[0].set(0.5);. What happens at runtime?Hardware Collections and Fault Logging
Implement the following class skeleton. It manages four drive motors as an array and maintains a runtime fault log as an ArrayList. No robot hardware required — you can run this as a standalone Java class with a main() method using mock values.
- Implement all eight TODOs. Every method that iterates over the array must use the appropriate loop style —
for-eachwhen the index isn't needed, indexedforwhen it is. - Write a
main()method that callssetCurrentReadings()with{30.0, 58.2, 42.1, 61.7}, thencheckForFaults(), thenprintFaultLog(). Verify that motors 1 and 3 are logged (0-indexed) and motors 0 and 2 are not. - Call
getMaxCurrent()and print the result. Confirm it returns61.7. - Call
hasMotorFault(3)andhasMotorFault(0)and print both results. Explain whycontains()works for this even though the fault message contains more than just the motor number. - Bonus: Modify
checkForFaults()to also compute and log the average current across all four motors in a single pass, appending it as a summary entry at the end of the fault log. What is the minimum number of array iterations required to do this without an extra loop?