Unit 6 · Lesson 8

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(), and Commands.deadline() to run actions concurrently with different completion rules
  • Compose nested groups to build multi-phase autonomous routines
  • Return a composed command from getAutonomousCommand() in RobotContainer
  • 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.

Commands.sequence(A, B, C) — timeline
Command A
running
Command B
running
Command C
running
Group ends
💡 Use this for: autonomous routines, multi-step scoring sequences, any "do A, then B" behavior

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.

Commands.parallel(A, B, C) — all run, group ends when ALL finish
Command A
running
Command B
running
Command C (last)
running → all done when C ends
💡 Use this for: spin up shooter AND raise arm simultaneously before scoring

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(A, B, C) — group ends when FIRST finishes
Command A (wins)
wins the race
Command B (cancelled)
cancelled
Command C (cancelled)
cancelled
💡 Use this for: intake until sensor fires OR 3 seconds elapse (safety timeout)

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."

Commands.deadline(A, B) — group ends when A (deadline) finishes
A (deadline)
deadline command
B (cancelled at A)
runs during A, cancelled when A ends
💡 Use this for: drive forward 36 inches while running the intake the whole time

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.

RobotContainer.java — getAutonomousCommand()
public Command getAutonomousCommand() {
  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");
}
← click a highlighted token
⚠️ Subsystem conflicts inside groups

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.

🔍 LRI Perspective: "Test every building block independently, first"

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.

🔌 System Check — Autonomous Group Testing Protocol

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?

  • A Commands.parallel() — runs both commands, ends when both finish.
  • B Commands.sequence() — runs them one after the other.
  • C Commands.deadline(driveCommand, intakeCommand) — the drive command is the deadline; the intake runs alongside and is cancelled when the robot reaches the target distance.
  • D Commands.race(driveCommand, intakeCommand) — ends when whichever finishes first, so the intake completing early would cut the drive short.

2. A Commands.sequence(DriveCommand, IntakeCommand) is running. The DriveCommand finishes but IntakeCommand has a bug where its isFinished() always returns false. What happens?

  • A The group times out after 30 seconds.
  • B The sequence hangs indefinitely on IntakeCommand, holding IntakeSubsystem for the rest of the match — unless the drive team disables the robot or TeleOp overrides it. There is no automatic timeout.
  • C The sequence cancels IntakeCommand after 1 second and continues.
  • D Commands.sequence() detects the stall and moves to the next command after 5 seconds.

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?

  • A parallel() groups cannot contain more than one command.
  • B Both commands require DriveSubsystem. WPILib detects duplicate subsystem requirements at group construction time and throws an exception — you cannot run two commands that claim the same subsystem in a parallel group.
  • C The DriveCommand constructor is missing a required argument.
  • D Commands.parallel() is not available in the version of WPILib being used.
💪 Practice Prompt — Unit 6 Capstone

Build a Full Autonomous Routine

  1. Write a DriveDistanceCommand that 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.
  2. Write a TurnToAngleCommand that 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.
  3. 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 the distancePerPulse constant or gyro-based turn logic — not the sequence structure.
  4. 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.
  5. Register this routine in RobotContainer's SendableChooser as "Drive + Collect". Add a second option: "Drive Only" using just the first drive command. Select each in Shuffleboard and confirm the correct routine runs.
  6. Stretch goal: Add a Commands.race(new IntakeCommand(m_intake), Commands.waitSeconds(2.0)) phase that replaces the plain IntakeCommand — 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.