Sequential & Parallel Command Groups
Individual commands are the atoms. Command groups are the molecules — and they are how you build autonomous routines. This lesson covers the four group types, when each one is the right tool, and how to compose them into the readable, testable, and robust autonomous code that wins matches.
By the end of this lesson, you will:
- Use
Commands.sequence()to chain actions that must execute one-after-another - Use
Commands.parallel(),Commands.race(), andCommands.deadline()to run actions concurrently with different completion rules - Compose nested groups to build multi-phase autonomous routines
- Return a composed command from
getAutonomousCommand()inRobotContainer - Explain the 2910 "build small, compose big" philosophy and why it enables faster debugging
- Identify subsystem requirement conflicts inside groups and explain how the scheduler handles them
Build Small, Compose Big
Every autonomous routine on every championship robot is built the same way: small, individually-tested commands composed into groups. The key insight is that a SequentialCommandGroup containing DriveDistanceCommand, IntakeCommand, and ShootCommand can be fully debugged at the component level. If the auto fails, you already know each building block works independently — the bug is in the sequencing or the transitions, not the underlying behavior.
If you instead write a single monolithic auto command with all the logic in one execute(), debugging requires running the entire routine to find any individual step's failure. At competition, where debugging time is measured in minutes, composition is the difference between finding the problem and guessing.
The Four Group Types
Explore each group type below. Each tab includes a timing diagram showing when commands run and end.
Commands.sequence() runs commands one after another. Command B does not start until Command A's isFinished() returns true. The group ends when the last command finishes. This is the backbone of every autonomous routine.
The most-used group type in FRC. Every auto that has steps uses sequence at the top level.
Commands.parallel() runs all commands simultaneously. The group ends when the last command finishes — it waits for all of them. Use this when two actions must both complete before proceeding.
Both commands must finish before the auto routine can proceed to the actual shot.
Commands.race() runs all commands simultaneously but ends as soon as the first one finishes. All remaining commands are cancelled (their end(true) fires). Use this for "whichever happens first" logic — like a timeout safety net or a sensor trip, whichever comes first.
Commands.race(new IntakeCommand(m_intake), Commands.waitSeconds(3.0)) — whichever ends first wins. The other is cancelled.
Commands.deadline(deadlineCmd, others...) runs all commands simultaneously but ends when the first argument (the deadline command) finishes. All other commands are cancelled. Use this for "do this supporting action for exactly as long as the main action takes."
The drive distance command is the deadline. The intake command runs alongside it and stops exactly when the robot reaches 36 inches — no more, no less.
Building an Autonomous Routine
Here is a complete, real-world autonomous routine assembled with command groups. The goal: drive to a game piece, pick it up, drive to the goal, and shoot. Click the highlighted tokens to understand each compositional decision.
return Commands.sequence(
// Phase 1: Reset sensors
Commands.runOnce(() -> {
m_drive.resetEncoders();
m_gyro.reset();
}),
// Phase 2: Drive to piece while running intake
Commands.deadline(
new DriveDistanceCommand(m_drive, 72), // deadline: 72 inches
new IntakeCommand(m_intake) // support: intake while driving
),
// Phase 3: Turn to face goal
new TurnToAngleCommand(m_drive, 180),
// Phase 4: Spin up shooter AND drive forward simultaneously
Commands.parallel(
new DriveDistanceCommand(m_drive, 24),
new SpinShooterCommand(m_shooter, 3500)
),
// Phase 5: Shoot — only if at speed (safety guard)
new ShootCommand(m_shooter, m_intake)
.withTimeout(1.5)
).withName("Score + Drive Auto");
}
Command groups obey the same subsystem requirement rules as individual commands. If you put two commands in a parallel() group that both require DriveSubsystem, WPILib will throw an exception at construction time — it detects the conflict before the routine ever runs. This is a feature: the error is caught at startup, not mid-match. If you see "Command group requires subsystem already required," check your group for duplicate subsystem claims.
Before I run a composed autonomous routine, I want to see each command run in isolation from the Shuffleboard test trigger. DriveDistanceCommand — does it reliably stop at 72 inches? TurnToAngleCommand — does it reliably stop at 180°? If I can't answer yes to both, no composition will save the auto. Composition only multiplies reliability. If your building blocks have a 5% failure rate each, a five-step sequence will fail roughly 23% of the time. Reliable building blocks first. Then compose.
Before running a full autonomous routine in competition:
- Run each member command individually from a Shuffleboard button or test trigger. Confirm each one ends correctly — encoder distance stops at target, sensor terminates at right moment, timer elapses properly. Do not skip this step.
- Test sequences on a smooth, level surface. Encoders accumulate error on rough surfaces. Gyro drift increases with vibration. These accumulate through a multi-step routine and cause later steps to fail even if earlier ones seem correct.
- Add
.withName("Phase N: Description")to each step in a long sequence. The Scheduler widget will show the active phase name during a test run — you'll know exactly which step is running when something fails. - Set a timeout on the entire auto sequence:
getAutonomousCommand().withTimeout(14.5). FRC autonomous is 15 seconds. If your routine hangs on a stuck sensor, the timeout prevents it from running into TeleOp.
Knowledge Check
1. You want to run an intake while simultaneously driving to a position. When the robot reaches the target distance, both actions should stop even if the intake hasn't picked up a game piece yet. Which group type should you use?
2. A Commands.sequence(DriveCommand, IntakeCommand) is running. The DriveCommand finishes but IntakeCommand has a bug where its isFinished() always returns false. What happens?
3. You build this group: Commands.parallel(new DriveCommand(m_drive, 48), new TurnCommand(m_drive, 90)). When you run it, WPILib throws a runtime error before the routine even starts. What is the cause?
Build a Full Autonomous Routine
- Write a
DriveDistanceCommandthat drives the robot forward until an encoder target is reached. Test it alone: confirm it stops at the measured distance within ±3 inches. Add.withName()so it's identifiable in the Scheduler widget. - Write a
TurnToAngleCommandthat turns the robot until the gyro reads a target angle. Test it alone. It should stop within ±3 degrees. Add a.withTimeout(3.0)safety net in case the gyro reads incorrectly. - Build a simple autonomous: drive 36 inches, turn 180°, drive 24 inches back. Use
Commands.sequence(). Measure the actual vs. intended endpoint. If it's off, adjust thedistancePerPulseconstant or gyro-based turn logic — not the sequence structure. - Add a parallel phase: while driving the first 36 inches, also run the intake. Restructure Phase 1 as a
Commands.deadline(driveCommand, intakeCommand). Verify the intake stops exactly when the robot stops, not before or after. - Register this routine in
RobotContainer'sSendableChooseras "Drive + Collect". Add a second option: "Drive Only" using just the first drive command. Select each in Shuffleboard and confirm the correct routine runs. - Stretch goal: Add a
Commands.race(new IntakeCommand(m_intake), Commands.waitSeconds(2.0))phase that replaces the plainIntakeCommand— the robot will try to pick up a piece for up to 2 seconds and move on regardless. This is the safety pattern for autonomous intake on a real competition robot: you try, but you don't get stuck waiting forever.