Skip to content
Programming, Controls & Sensors·Lesson 11 of 51

Writing Commands and Compositions

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

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.

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.

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

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

3.Why are factory methods (returning a fresh command instance) preferred when building command compositions?

Answer every question to submit.