Using Finite State Machines in Go with the looplab/fsm Package
This article introduces the concept of finite state machines, demonstrates how to implement them in Go using the looplab/fsm library with practical code examples, explains the lifecycle callbacks, and shows how to apply FSMs to real‑world scenarios such as door state management.
In programming, a finite state machine (FSM) is an elegant tool for managing complex state transitions, defined by states, events, and transition rules that modularize business logic. This article explores how to use FSMs in Go.
Finite State Machine
Before introducing FSMs, consider a simple example program:
https://github.com/jianghushinian/blog-go-example/blob/main/fsm/main.go
package main
import (
"fmt"
)
type State string
const (
ClosedState State = "closed"
OpenState State = "open"
)
type Event string
const (
OpenEvent Event = "open"
CloseEvent Event = "close"
)
type Door struct {
to string
state State
}
func NewDoor(to string) *Door {
return &Door{to: to, state: ClosedState}
}
func (d *Door) CurrentState() State { return d.state }
func (d *Door) HandleEvent(e Event) {
switch e {
case OpenEvent:
d.state = OpenState
case CloseEvent:
d.state = ClosedState
}
}
func main() {
door := NewDoor("heaven")
fmt.Println(door.CurrentState())
door.HandleEvent(OpenEvent)
fmt.Println(door.CurrentState())
door.HandleEvent(CloseEvent)
fmt.Println(door.CurrentState())
}The example defines a core struct Door with fields to (the destination of the door) and state (the current state, either open or closed ). The main function creates a door, prints its initial state, triggers an open event, prints the new state, then triggers a close event and prints the final state.
$ go run main.go
closed
open
closedThis demonstrates that a door is a concrete model of an FSM.
An FSM (finite‑state machine) is a mathematical computation model with three core characteristics:
• The number of states is finite.
• At any moment the system is in exactly one state.
• Under certain conditions (triggered by an event) the system transitions from one state to another.
Any object satisfying these three properties can be regarded as an FSM. For the Door object, the states are open and closed ; an open event moves the door from closed to open , and a close event does the opposite.
FSMs appear everywhere in daily life—traffic lights, order processing in e‑commerce, etc.
Using the looplab/fsm Package
Installation
Install the package with:
$ go get github.com/looplab/fsmSimple Usage
Rewrite the earlier Door example using the fsm package:
https://github.com/jianghushinian/blog-go-example/blob/main/fsm/examples/simple.go
package main
import (
"context"
"fmt"
"github.com/looplab/fsm"
)
func main() {
fsm := fsm.NewFSM(
"closed",
fsm.Events{
{Name: "open", Src: []string{"closed"}, Dst: "open"},
{Name: "close", Src: []string{"open"}, Dst: "closed"},
},
fsm.Callbacks{},
)
fmt.Println(fsm.Current())
_ = fsm.Event(context.Background(), "open")
fmt.Println(fsm.Current())
_ = fsm.Event(context.Background(), "close")
fmt.Println(fsm.Current())
}The fsm.NewFSM function creates an FSM object. The first argument is the initial state, the second is a slice of events defining source and destination states, and the third is a map of callbacks (empty in this simple example).
$ go run examples/simple.go
closed
open
closedUsing the fsm package abstracts the state‑transition logic, reducing bugs and improving maintainability.
Embedding FSM in a Struct
The package can also be used as a field inside a struct:
https://github.com/jianghushinian/blog-go-example/blob/main/fsm/examples/struct/struct.go
package main
import (
"context"
"fmt"
"github.com/looplab/fsm"
)
type Door struct {
To string
FSM *fsm.FSM
}
func NewDoor(to string) *Door {
d := &Door{To: to}
d.FSM = fsm.NewFSM(
"closed",
fsm.Events{{Name: "open", Src: []string{"closed"}, Dst: "open"}, {Name: "close", Src: []string{"open"}, Dst: "closed"}},
fsm.Callbacks{"enter_state": func(_ context.Context, e *fsm.Event) {d.enterState(e)}},
)
return d
}
func (d *Door) enterState(e *fsm.Event) {
fmt.Printf("The door to %s is %s\n", d.To, e.Dst)
}
func main() {
door := NewDoor("heaven")
_ = door.FSM.Event(context.Background(), "open")
_ = door.FSM.Event(context.Background(), "close")
}Here the FSM is a field of Door , and a callback prints a message whenever the state changes.
$ go run examples/struct/struct.go
The door to heaven is open
The door to heaven is closedThe package provides eight lifecycle callbacks. They are grouped into four categories: before (executed before an event), leave (executed when leaving a state), enter (executed when entering a state), and after (executed after an event). The article lists each callback (e.g., before_open , leave_closed , enter_open , after_open ) and explains their execution order and precedence.
Callbacks can be defined with shorthand keys such as "closed" for enter_closed or "close" for after_close . The article also shows how to handle unknown events or states.
Conclusion
The article introduced FSM concepts, demonstrated how to implement a door state machine in Go using the fsm library, and highlighted the benefits of pre‑defining transition rules and lifecycle callbacks to reduce bugs and improve code clarity. It points readers to further resources, including the official fsm examples, the OneX nightwatch component, and the source repository.
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.