Arrays & Methods
storing lists of data and writing reusable blocks of code. methods are your best friend :)
Arrays
ok so before last week you learned loops, and you were probably thinking "ok cool, i can repeat stuff." but repeat stuff on WHAT? loops are way more useful when you have a collection of data to work through. that's where arrays come in. an array is just a fixed-size list where every slot holds the same type of thing.
once you have arrays AND loops together, you can do things like "apply this formula to all four drivetrain (the drivetrain is the system of motors and wheels that makes the robot move) motors" in four lines instead of sixteen. that combo is one of the most used patterns in all of FRC code.
arrays — the locker row mental model
imagine a row of lockers at school. each locker has a number painted on the door (0, 1, 2, 3...) and can hold exactly one thing inside. all the lockers in the row hold the same type of stuff — you can't have a locker full of text next to a locker full of numbers. the whole row is one unit — one array.
that number painted on the door is called the index. the thing inside the locker is the value. you get to the value by specifying which locker number you want: lockers[2] means "open locker number 2 and give me what's inside."
now think about real FRC code. a swerve drivetrain (swerve = drivetrain where each wheel steers independently) has four modules. each module has a motor with a CAN ID (CAN ID = the unique number that identifies each motor on the robot's wiring network). before arrays, you'd track each one with a separate variable:
// tracking four motors with four separate variables — already painful int motor1 = 1; int motor2 = 2; int motor3 = 3; int motor4 = 4; // now imagine trying to loop through them. you literally can't. // imagine passing them all to a method. nightmare. // imagine changing all four to a new base CAN ID. hunt and replace, four times.
with an array, you collapse all four into one thing you can loop through, pass around, and update in one place:
// one variable, four values, perfectly loopable int[] motorIDs = {1, 2, 3, 4};
that's the whole pitch for arrays. one variable, ordered list, same type throughout. let's get into the details.
two ways to declare an array
there are two common patterns for creating an array in Java, and you'll see both in real code. the first is for when you know the size but want to fill it in later. the second is for when you already have the values ready to go.
// Way 1: declare the size, fill it in slot by slot // Java fills all empty slots with 0 for int/double, false for boolean, null for objects int[] arr = new int[4]; // creates 4 slots, all set to 0 right now arr[0] = 10; // assign slot 0 arr[1] = 11; // assign slot 1 arr[2] = 12; // assign slot 2 arr[3] = 13; // assign slot 3 // Way 2: declare and fill in one shot — size is inferred from the list int[] motorIDs = {10, 11, 12, 13}; // 4 ints double[] moduleAngles = {0.0, 90.0, 180.0, 270.0}; // 4 doubles boolean[] sensorStates = {false, false, true, false}; // 4 booleans
the syntax int[] means "an array of ints." the square brackets after the type are the signal to Java that this is an array, not a single value. you can also write int arr[] with the brackets after the name, but everyone writes the brackets after the type. go with that style.
use Way 1 when your values come from somewhere else at runtime (like reading from sensors). use Way 2 when you know your values at compile time (like constants for CAN IDs or module offsets).
zero indexing — read this twice
this is the single most common beginner mistake. please read carefully because this trips up almost everyone the first few times.
in Java, arrays start counting at zero, not one. if you have a 4-element array, the valid indices are 0, 1, 2, 3. there is no index 4. the first element is always at index 0, the last is always at index array.length - 1. this is called zero-based indexing and it is universal across nearly every programming language.
String[] modules = {"FL", "FR", "BL", "BR"}; // [0] [1] [2] [3] // ↑ first last ↑ // (NOT 1!) (NOT 4!) System.out.println(modules[0]); // "FL" — index 0, the first element System.out.println(modules[1]); // "FR" — index 1 System.out.println(modules[2]); // "BL" — index 2 System.out.println(modules[3]); // "BR" — index 3, the last element // changing a value is the same syntax as reading, just with = on the right modules[2] = "BackLeft"; // slot 2 now holds "BackLeft"
quick trick you'll use constantly: the last valid index is always arr.length - 1. if there are 4 elements, last index is 3. if there are 10 elements, last index is 9. whenever you need the last element, write arr[arr.length - 1] and you'll never be wrong.
the .length property
every array has a built-in property called .length that tells you exactly how many slots it has. notice: no parentheses. it's a property, not a method call. arr.length, not arr.length(). (this trips people up because String has a .length() method with parentheses. arrays are different.)
double[] speeds = {0.5, 0.75, 1.0, -0.5}; System.out.println(speeds.length); // 4 — counts total slots, not last index // NOTE: last valid INDEX is 3, but LENGTH is 4. classic confusion point. // .length in a for loop — this pattern is everywhere in FRC code for (int i = 0; i < speeds.length; i++) { System.out.println("Speed at index " + i + ": " + speeds[i]); } // prints: // Speed at index 0: 0.5 // Speed at index 1: 0.75 // Speed at index 2: 1.0 // Speed at index 3: -0.5
using arr.length in your loop condition instead of a hardcoded number is the right habit to build. if you ever resize the array, the loop automatically adjusts — you don't have to hunt down every place you hardcoded the size.
ArrayIndexOutOfBoundsException — the crash you will definitely see
story time. you have a 4-element array. you're confident the last index is 4, because there are 4 elements. you write arr[4]. the compiler says nothing. you run the program. it immediately crashes with this error:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4
this is one of the most common runtime errors in Java. the compiler cannot catch it because the index might not be known until the program actually runs. Java has to wait and see — and when it sees an invalid index, it crashes.
int[] motorIDs = {10, 11, 12, 13}; // valid indices: 0, 1, 2, 3 System.out.println(motorIDs[4]); // CRASH — index 4 doesn't exist System.out.println(motorIDs[-1]); // CRASH — negative indices are also invalid // BAD loop that crashes — uses <= instead of < for (int i = 0; i <= motorIDs.length; i++) { // on last loop: i=4, CRASH System.out.println(motorIDs[i]); } // GOOD loop — uses strictly less than (<) for (int i = 0; i < motorIDs.length; i++) { // stops at i=3, perfectly safe System.out.println(motorIDs[i]); } // safely getting the last element System.out.println(motorIDs[motorIDs.length - 1]); // 13 — always safe
ArrayIndexOutOfBoundsException: the two most common causes are (1) using <= instead of < in your loop condition, and (2) using arr.length as an index directly instead of arr.length - 1. both stem from forgetting that indices start at 0 and end at length minus 1, not length.
looping through arrays — tying it all together
arrays and for loops are a combo you'll use constantly. the pattern is always the same: loop from 0 to arr.length - 1 (using i < arr.length), and access each element with arr[i]. here's a real FRC example: you have two parallel arrays, one for CAN IDs and one for module offset angles. you want to print info for every module:
final int[] kModuleCANIDs = {10, 11, 12, 13}; final double[] kModuleAngles_deg = {0.0, 90.0, 180.0, 270.0}; // both arrays have .length == 4, so this loop runs exactly 4 times (i = 0,1,2,3) for (int i = 0; i < kModuleCANIDs.length; i++) { System.out.println( "Module " + i + ": CAN ID = " + kModuleCANIDs[i] + // same index, different array ", offset angle = " + kModuleAngles_deg[i] + "°" ); } // Module 0: CAN ID = 10, offset angle = 0.0° // Module 1: CAN ID = 11, offset angle = 90.0° // Module 2: CAN ID = 12, offset angle = 180.0° // Module 3: CAN ID = 13, offset angle = 270.0°
this "parallel arrays" pattern — two arrays with matching indices where arr1[i] and arr2[i] describe the same thing — shows up a lot in FRC. once you get to OOP next week, you'll learn how to bundle those two together into one object. but arrays first.
Topic 1 — Coding Prompt
Declare these two arrays:double[] motorTemps = {42.1, 38.5, 51.0, 44.8};String[] moduleNames = {"FL", "FR", "BL", "BR"};
Write a for loop that prints each module on one line like: "FL: 42.1°C"
Use the same index variable to access both arrays at once. After the loop, print the total number of modules using .length.
Topic 1 — Quick Check
Methods
ok so now you can write code that loops, stores lists, and makes decisions. but there's a big problem you'll hit almost immediately: copy-paste. you end up writing the same formula or logic in three different places in your file. then you find a bug in it, and now you have to fix it in three places. miss one and your robot does math wrong at a competition.
methods solve this completely. a method is a named block of code that you write once and can call as many times as you want from anywhere. fix it in the method, fixed everywhere.
the anatomy of a method
think of a method like a recipe. you write the pancake recipe once in a cookbook. then whenever you want pancakes, you just say "make pancakes." you don't re-write the whole recipe every time you want breakfast. the recipe is the method. "make pancakes" is calling it.
here's the non-pancake version. say you need to calculate wheel RPM from motor RPM at three different places in your code. without methods, that's three copies of the same formula:
// Place 1: in driveForward() double wheelRPM1 = motorRPM1 / 8.46; // Place 2: in driveRotate() double wheelRPM2 = motorRPM2 / 8.46; // Place 3: in getTelemetry() double wheelRPM3 = motorRPM3 / 8.46; // ...oops. the real gear ratio is 8.14, not 8.46. // now you need to fix it in 3 places. miss one and your robot math is wrong.
with a method, you write the formula once. when the gear ratio changes, you fix one line and everywhere is instantly correct:
// write the recipe once — fix gear ratio here, fixed everywhere public static double calcWheelRPM(double motorRPM, double gearRatio) { return motorRPM / gearRatio; } // call it from anywhere, as many times as you want double wheelRPM1 = calcWheelRPM(motorRPM1, 8.46); double wheelRPM2 = calcWheelRPM(motorRPM2, 8.46); double wheelRPM3 = calcWheelRPM(motorRPM3, 8.46);
method anatomy — every piece explained
you need to be able to look at any method signature and immediately know what every word means. let's go piece by piece through a real example. each number below corresponds to a label in the code:
// [1] [2] [3] [4] [5] [5] public static double calcWheelRPM(double motorRPM, double gearRatio) { return motorRPM / gearRatio; // [6] — the body, and return statement } // closing brace ends the method
public means any code anywhere can call this method. private limits it to only code inside the same class. start with public for now — we go deeper on this in the OOP week.static means this method belongs to the class itself, not to a specific object. you can call it as ClassName.calcWheelRPM(...) without creating an object first. utility methods are almost always static.void if it does something but doesn't give you a value back. the type here must match what you return inside the body.setX(), getX(), or action words like spin(), stop(), extend().return ends the method immediately and sends a value back. the type of the value must match the declared return type. for void methods, you can omit return entirely or write bare return;.void methods — doing things without returning anything
not every method hands back a value. sometimes a method just does something — sets a motor speed, prints some info to the console, runs an animation. for those, you use void as the return type. void literally means "nothing" — this method gives nothing back.
// void method — does something useful, returns nothing public static void printSpeed(double speed) { System.out.println("Current speed: " + speed); // no return statement needed — Java auto-returns when the block ends // (you CAN write bare "return;" to exit early, but it's optional here) } // returning method — calculates something and hands the result back public static double clampSpeed(double speed, double maxSpeed) { if (speed > maxSpeed) return maxSpeed; // early return if too high if (speed < -maxSpeed) return -maxSpeed; // early return if too low return speed; // already in range, return as-is } // calling them — notice the difference printSpeed(0.75); // called as a statement — no assignment double safeSpeed = clampSpeed(1.5, 1.0); // safeSpeed = 1.0 (was clamped) double alsoSafe = clampSpeed(0.8, 1.0); // alsoSafe = 0.8 (already in range)
you cannot do double x = printSpeed(0.75); — there's nothing to assign because printSpeed returns void. the compiler will tell you "incompatible types: void cannot be converted to double." if you need to both do something AND get a value back, use a return type other than void.
parameters vs arguments — the distinction that trips everyone up
these two words get used interchangeably all the time, but they actually mean different things. here's the one-line version: a parameter is the placeholder in the method's definition. an argument is the actual value you pass in when you call it.
double motorRPM and double gearRatio in the signature are parameters. they're like the blanks in a recipe: "add ___ cups of flour." the recipe doesn't know the number yet.5400.0 and 8.46 when you write calcWheelRPM(5400.0, 8.46) are arguments. they're the actual ingredients filling in the blanks in the recipe.in everyday conversation, people say "parameter" to mean both. that's fine. but in an interview or a code review, knowing the technical distinction shows you actually understand what's happening under the hood.
methods calling other methods
this is where things get powerful. methods can call other methods. you break down complex behavior into small, understandable pieces, and then compose them together. in FRC you'll see this all the time — a high-level shoot() method might call spinUpFlywheels(), then wait for isAtSpeed(), then call activateIndexer().
// step 1: a focused method that does one thing public static double motorRPMtoWheelRPM(double motorRPM, double gearRatio) { return motorRPM / gearRatio; } // step 2: another focused method that does one thing public static double wheelRPMtoSpeed_fps(double wheelRPM, double wheelDiam_in) { double circumference_ft = Math.PI * wheelDiam_in / 12.0; // convert inches to feet return wheelRPM * circumference_ft / 60.0; // RPM to fps } // step 3: a higher-level method that calls both of the above public static double motorRPMtoSpeed_fps(double motorRPM, double gearRatio, double wheelDiam_in) { double wheelRPM = motorRPMtoWheelRPM(motorRPM, gearRatio); // call #1 return wheelRPMtoSpeed_fps(wheelRPM, wheelDiam_in); // call #2 // reading this, you can understand what it does without seeing the math }
each of those small methods is easy to test in isolation and easy to read. the composed version reads almost like English. this is the core idea behind good code structure — small, focused pieces that you build up into bigger behavior.
WRT naming patterns: in our codebase, methods fall into categories. setX() methods change state (like setSpeed(double speed)). getX() methods read state (like getPosition()). action methods describe mechanism behavior: spin(), stop(), extend(), retract(). when naming a method, pick a verb that describes exactly what it does. vague names like calc() or doThing() are not acceptable in real code review.
common method mistakes (this WILL bite you)
three mistakes that beginners hit over and over. recognize these so you can fix them in 10 seconds instead of staring at the error for 20 minutes.
// MISTAKE 1: forgetting the return statement // compiler says: missing return statement public static double addValues(double a, double b) { double result = a + b; // oops — computed result but never returned it // fix: add "return result;" here } // MISTAKE 2: return type doesn't match what you actually return // compiler says: incompatible types: double cannot be converted to int public static int getSpeed() { return 0.75; // 0.75 is a double, but signature says int — mismatch // fix: either change return type to double, or return (int) 0.75 } // MISTAKE 3: calling with wrong argument types // compiler says: method calcWheelRPM(double,double) not applicable for arguments (String,double) public static double calcWheelRPM(double motorRPM, double gearRatio) { return motorRPM / gearRatio; } calcWheelRPM("5400", 8.46); // "5400" is a String — wrong type, compile error // fix: pass the number as a number: calcWheelRPM(5400.0, 8.46)
Topic 2 — Coding Prompt
Write a static method called clampSpeed(double speed, double max) that prevents speeds from going out of range:
• if speed is above max → return max
• if speed is below -max → return -max
• otherwise → return speed unchanged
In main, test it by printing these calls:clampSpeed(1.3, 1.0) → should return 1.0clampSpeed(-1.1, 1.0) → should return -1.0clampSpeed(0.5, 1.0) → should return 0.5
Topic 2 — Quick Check
Javadocs & Code Documentation
story time. it's 11pm the night before a regional competition. a motor controller is behaving weird and you need to dig into the subsystem code to figure out what's wrong. you open the file and you see this:
public static double calc(double x, double y) { return x / y; } public static double conv(double a, double b, double c) { return a * Math.PI * b / 12.0 * c / 60.0; }
what does calc do? what are x and y in units? what does conv return? no idea. you wrote this code two months ago and now nobody — including you — can read it. you have to dig through every call site to piece together what it does. at 11pm. at a competition. this is a real situation that happens to real teams.
good documentation is how you avoid this entirely. let's look at all three comment types and then go deep on the one that actually matters for team code.
the three comment types in Java
// 1. Single-line comment — use for quick inline explanations // starts with // and goes to the end of the line // compiler completely ignores everything after // double safeSpeed = Math.min(speed, 1.0); // clamp — TalonFX faults above 1.0 /* * 2. Block comment — spans multiple lines * use when you want to explain a whole section, * or temporarily disable a chunk of code during debugging * starts with slash-asterisk, ends with asterisk-slash */ /** * 3. Javadoc comment — this is the important one for team code * same as a block comment but with TWO asterisks to start * IDEs like VS Code and IntelliJ show this as a hover tooltip * place one directly above every public class and every public method * * @param speed the target speed in range -1.0 to 1.0 * @return true if the motor reached the target speed */ public boolean setSpeed(double speed) { // implementation here... return true; }
the difference between /* ... */ and /** ... */ is literally one asterisk. but that one asterisk tells IDEs and documentation generators to treat it specially. always use /** for method documentation, not /*.
Javadoc in full — @param and @return
Javadoc is what turns your comments into actual living documentation. hover over a method call in VS Code after adding a Javadoc and you'll see a tooltip pop up with the description, parameters, and return value. your teammates don't have to scroll to the method definition to understand how to use it — the tooltip appears right there.
/** * Calculates wheel RPM from a motor's RPM and the drivetrain gear ratio. * Use this any time you need to convert raw motor velocity to actual wheel speed. * The gear ratio is defined as motor turns per wheel turn (e.g. 8.46 means * the motor spins 8.46 times for every 1 rotation of the wheel). * * @param motorRPM the motor's rotational speed in RPM (Falcon 500 free speed: ~6380) * @param gearRatio motor turns per wheel turn — higher ratio = more torque, less speed * @return the resulting wheel speed in RPM as a double */ public static double motorRPMtoWheelRPM(double motorRPM, double gearRatio) { return motorRPM / gearRatio; } /** * Clamps a speed value to a safe operating range. * TalonFX motor controllers fault if commanded above 1.0 or below -1.0. * Always clamp before passing values to motor.set(). * * @param speed the raw speed to clamp (can be any double) * @param maxSpeed the maximum allowed magnitude, usually 1.0 * @return speed clamped to the range [-maxSpeed, maxSpeed] */ public static double clampSpeed(double speed, double maxSpeed) { if (speed > maxSpeed) return maxSpeed; if (speed < -maxSpeed) return -maxSpeed; return speed; }
the tags you need to know: @param documents one input — one line per parameter, name then description. @return documents what gets handed back. there's also @throws for documenting exceptions, but @param and @return cover 90% of what you'll write. also: put the @param lines in the same order as the parameters appear in the signature.
class-level Javadoc
it's not just methods that get Javadocs — public classes do too. a class-level Javadoc sits above the class declaration and describes what the class represents, what it's responsible for, and any important notes about how to use it.
/** * Utility class for swerve drivetrain calculations. * Contains static methods for converting between encoder ticks, RPM, and * real-world speeds and distances. All methods are stateless (no instance needed). * * Used by: DriveSubsystem, autonomous routines, telemetry dashboards (telemetry = real-time data from the robot displayed on the driver's dashboard). */ public class DriveCalculator { /** * Converts encoder ticks to wheel revolutions. * @param ticks raw encoder tick count from TalonFX * @param ticksPerRev encoder ticks per full motor revolution (TalonFX = 2048) * @return total revolutions as a double */ public static double ticksToRevolutions(int ticks, int ticksPerRev) { return (double) ticks / ticksPerRev; // cast to avoid integer division } }
good comments vs bad comments
here's the thing — writing useless comments is almost as bad as writing no comments at all. a bad comment just restates what the code obviously already says. a good comment explains WHY something is done, or gives context that isn't obvious from reading the code itself.
// BAD: tells me what I can already see. zero extra information. double clamped = Math.min(speed, 1.0); // set clamped to min of speed and 1.0 // GOOD: tells me WHY — and includes the specific thing to watch out for double clamped = Math.min(speed, 1.0); // clamp — TalonFX faults above 1.0 // BAD: the number 8.46 is magic — where did it come from? when does it change? double wheelRPM = motorRPM / 8.46; // GOOD: the constant is named and the comment explains the physical meaning final double kGearRatio = 8.46; // L2 SDS MK4i gear ratio (motor turns per wheel turn) double wheelRPM = motorRPM / kGearRatio; // BAD: vague javadoc that adds nothing — you could have just read the signature /** * calculates rpm * @param a the a * @param b the b * @return something */ // GOOD: specific enough that a teammate understands without reading the body /** * Converts motor RPM to actual wheel surface speed in feet per second. * Uses wheel circumference (pi * diameter / 12 for inches to feet) and * divides by 60 to convert from per-minute to per-second. * * @param wheelRPM wheel rotational speed in RPM (after gear reduction) * @param wheelDiam_in wheel outer diameter in inches (e.g. 4.0 for 4" wheels) * @return surface speed in feet per second */
WRT rule: every public method gets a Javadoc. every tricky calculation or non-obvious logic gets an inline comment explaining WHY. if you submit code for the real robot codebase, undocumented public methods will be flagged in code review and sent back. this is not optional.
Topic 3 — Coding Prompt
The starter code has three methods but zero documentation. Your job:
1. Add a Javadoc comment above the class explaining what DriveCalculator does
2. Add a Javadoc above each method with @param for each input and @return describing the output
3. Add one inline comment inside each method explaining WHY that math works, not just what it does
4. Rename the constant to follow WRT convention: k prefix + a units suffix (e.g. kTicksPerRev)
Topic 3 — Quick Check
Fill in the Blanks
public static setSpeed(double speed) { }
public static int add(int a, int b) { }
int val = arr[];
Knowledge Check
Coding Challenge
Write two static methods for drivetrain math:
1. motorToWheelRPM(double motorRPM, double gearRatio) — returns motorRPM / gearRatio
2. wheelRPMtoSpeed(double wheelRPM, double wheelDiam_in) — returns wheel surface speed in ft/s using: Math.PI * wheelDiam_in / 12.0 * wheelRPM / 60.0
In main, call both with: motorRPM = 5400, gearRatio = 8.46, wheelDiam_in = 4.0. Print the wheel RPM and the speed in ft/s.
Add a @param and @return Javadoc comment to each method.
Create DriveCalculator.java in your minibot-project folder. This utility class handles all the math your drivetrain needs — converting between encoder ticks (raw sensor counts), RPMs, and real-world distances.
static double ticksToRevolutions(int ticks, int ticksPerRev)— divide ticks by ticksPerRev, return as doublestatic double revolutionsToInches(double revolutions, double wheelDiameterInches)— multiply by Math.PI * diameterstatic double motorRPMtoWheelRPM(double motorRPM, double gearRatio)— divide motorRPM by gearRatiostatic double[] processModuleSpeeds(double[] rawSpeeds, double maxSpeed)— loop through and clamp each speed to [-maxSpeed, maxSpeed], return cleaned array- Add full Javadoc to every method including @param and @return
- In
main: test with a TalonFX (2048 ticks/rev), gear ratio 8.46, wheel diameter 4 inches
Weekly Test
covers everything from week 4. score gets sent to the leads :) try without looking back first!!