Mastering the Command Pattern: Implementing Undo, Redo, and Batch Transactions in Python

ArjanCodes////4 min read

Overview: Encapsulating Intent

The is a behavioral powerhouse that transforms a request into a stand-alone object. This shift matters because it decouples the object that invokes the operation from the one that knows how to perform it. Instead of calling a method directly on a data object, you wrap that action in a "command" object. This provides immense control over the execution lifecycle, allowing you to queue operations, log them, or pass them around like any other piece of data. In a banking context, where every cent counts, this pattern provides the rigorous structure needed to manage complex .

Prerequisites

To get the most out of this tutorial, you should be comfortable with fundamentals, specifically Object-Oriented Programming (OOP) concepts like classes and inheritance. Familiarity with (structural subtyping) is helpful, as we use them to define our command interface. You should also understand basic data structures like lists and dictionaries, which act as our "stacks" for undo and redo history.

Mastering the Command Pattern: Implementing Undo, Redo, and Batch Transactions in Python
Real-Life Case of the Command Design Pattern

Key Libraries & Tools

  • 3.8+: The primary language used for implementation.
  • : A standard library module used to reduce boilerplate code when creating data-heavy objects like bank accounts and commands.
  • : Used to define the Transaction interface, ensuring that any command we create adheres to the required method signatures.

Building the Foundation: The Command Protocol

We start by defining what a transaction looks like. Instead of a concrete class, we use a Protocol. This allows for flexible implementation across different types of banking actions.

from typing import Protocol

class Transaction(Protocol):
    def execute(self) -> None:
        ...
    def undo(self) -> None:
        ...
    def redo(self) -> None:
        ...

This interface forces every command—whether it is a deposit, withdrawal, or transfer—to know how to perform its action, reverse it, and repeat it. By defining these methods upfront, we prepare our system for non-destructive editing and history management.

Implementing Concrete Commands

Each banking operation becomes a concrete class. Take the Deposit command: it holds a reference to the Account and the amount. It doesn't just perform the math; it stores the state necessary to undo that math later.

@dataclass
class Deposit:
    account: Account
    amount: int

    def execute(self) -> None:
        self.account.deposit(self.amount)
        print(f"Deposited ${self.amount/100:.2f}")

    def undo(self) -> None:
        self.account.withdraw(self.amount)
        print(f"Undid deposit of ${self.amount/100:.2f}")

    def redo(self) -> None:
        self.execute()

The logic for a Transfer is slightly more complex as it involves two accounts, but the pattern remains identical. The execute method withdraws from one and deposits into another, while undo simply swaps those roles.

The Bank Controller: Managing the Stack

To handle undo and redo, we need a central manager. The BankController maintains two stacks: undo_stack and redo_stack. When you execute a command through the controller, it clears the redo history and pushes the new command onto the undo stack. This is the same logic used in professional and .

class BankController:
    undo_stack: list[Transaction] = field(default_factory=list)
    redo_stack: list[Transaction] = field(default_factory=list)

    def execute(self, transaction: Transaction):
        transaction.execute()
        self.redo_stack.clear()
        self.undo_stack.append(transaction)

    def undo(self):
        if not self.undo_stack: return
        transaction = self.undo_stack.pop()
        transaction.undo()
        self.redo_stack.append(transaction)

This architecture ensures that the user can never "redo" an old action after they have performed a brand-new operation, preventing state corruption.

Practical Examples: Batch Processing and Rollbacks

A major advantage of the is the ability to group commands into a Batch. In banking, you might want to perform five transfers as a single unit. If the third transfer fails due to insufficient funds, you must roll back the first two to maintain data integrity. Our Batch command handles this by iterating through its internal list of commands and using a try...except block to trigger undo() on completed steps if an error occurs.

Tips & Gotchas

  • Assumption of Success: In our basic implementation, we assume undo() and redo() will always succeed. In production systems, you must handle errors during these phases. If an undo fails, the system state is potentially compromised.
  • Granularity: Keep your commands small. Instead of a "Pay Bills" command that does everything, create a Batch of individual "Withdrawal" commands. This makes debugging and reversing specific parts of the process much easier.
  • Memory Management: If a user performs thousands of operations, your undo stack will grow indefinitely. Consider implementing a maximum stack size to prevent excessive memory consumption.
Topic DensityMention share of the most discussed topics · 13 mentions across 11 distinct topics
15%· concepts
15%· languages
8%· concepts
8%· libraries
8%· concepts
Other topics
46%
End of Article
Source video
Mastering the Command Pattern: Implementing Undo, Redo, and Batch Transactions in Python

Real-Life Case of the Command Design Pattern

Watch

ArjanCodes // 32:25

On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!

What they talk about
AI and Agentic Coding News
Who and what they mention most
Python
33.3%5
Python
20.0%3
Python
20.0%3
Pydantic
13.3%2
4 min read0%
4 min read