Skip to content
2 min read
In progress

Writing Commands and Compositions

Create commands with lambdas and factory methods, then combine them into sequences and parallel groups.

Lesson
11 / 51
Reward
+10XP

Sign in to track progress, earn XP, and save lessons.

What a command is

A command has four lifecycle pieces: initialize() (once at start), execute() (every loop), isFinished() (returns true to end), and end(boolean interrupted) (cleanup). A command runs from when it's scheduled until it finishes or is interrupted.

You can write a full command class, but modern WPILib strongly encourages lambdas and factory methods instead — they cover almost every use case with far less code.

Inline commands with factories

The Commands utility class and subsystem helper methods build commands inline:

// Run the intake until the button is released (runEnd: run, then stop)
Command intakeCmd = intake.runEnd(intake::run, intake::stop);

// A command that does one thing and finishes immediately
Command stopCmd = Commands.runOnce(intake::stop, intake);

// Run until a condition is met
Command raiseCmd = arm.run(arm::raise).until(arm::atTop);

Common building blocks:

  • run(...) — runs an action every loop (never finishes on its own).
  • runOnce(...) — runs once and ends.
  • runEnd(run, end) — runs an action, then runs an end action when it stops.
  • .until(condition) — adds an end condition to any command.
  • .withTimeout(seconds) — ends after a time limit.

Using the subsystem's own run/runEnd factory methods automatically adds that subsystem as a requirement.

Compositions: building bigger behavior

Commands are composable — you combine small commands into bigger ones. The main compositions:

  • Sequential — run commands one after another:
    Command auto = Commands.sequence(
        drivetrain.driveForward(2.0),
        arm.raiseToScore(),
        intake.eject());
    
  • Parallel — run commands at the same time. Variants include parallel() (waits for all), race() (ends when the first ends), and deadline() (ends when one specific "deadline" command ends).
  • Decorators.andThen(...), .alongWith(...), .raceWith(...) chain commands fluently:
    arm.raiseToScore().andThen(intake.eject());
    

Because a composition is itself a command, you can nest compositions inside compositions — a full autonomous routine is just one big command.

A practical pattern

Keep command factories as methods on the subsystem that returns them:

public class Intake extends SubsystemBase {
  public Command intakeCommand() { return runEnd(this::run, this::stop); }
}

Then wiring becomes a one-liner: driver.a().whileTrue(intake.intakeCommand()); (covered next). This keeps behavior close to the hardware it controls and keeps RobotContainer readable.

When to write a class

Write a dedicated command class only when logic is genuinely complex (lots of state, multiple phases) and a lambda would be unreadable. For most actions, inline factories are cleaner and less error-prone.

Spot an error or something out of date?Log in to suggest an edit

Key takeaways

  • A command has initialize / execute / isFinished / end; most are built with lambdas instead.
  • Use factory methods (run, runOnce, runEnd) and decorators (.until, .withTimeout, .andThen).
  • Compositions (sequence, parallel, race, deadline) combine commands into bigger behavior.
  • Subsystem factory methods auto-add the subsystem as a requirement.
  • Write a full command class only for genuinely complex, multi-phase logic.

Lesson quiz

Required

Answer all 3 questions correctly to complete this lesson.

01.In a Command's lifecycle, when is the execute() method called?

02.What does the boolean parameter in end(boolean interrupted) tell you?

03.Why is it a good pattern to keep command factory methods on the subsystem that returns them?

Answer every question to submit.

All 51 lessons in Programming, Controls & Sensors