Patterns: behavior · 3 MIN READ

State: behavior follows a valid lifecycle

Represent legal transitions instead of scattered boolean combinations.

Represent legal transitions instead of scattered boolean combinations. State makes behavior depend on an explicit lifecycle state. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.

Behavioral pattern · A MAP OF THE IDEA

  1. Current state limits operations
  2. Transition validates its precondition
  3. New state changes allowed behavior

Useful when

  • Lifecycle rules are scattered across conditionals
  • Invalid transitions should be explicit errors

Watch out

  • Persistence needs concurrency and retry rules
  • Separate state types can overcomplicate a small enum

Implementation & operation cost: Medium

Fit depends on the task

Small tool or CLI3/5
Domain-rich application5/5
Independent distributed services4/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

The design decision

State makes behavior depend on an explicit lifecycle state. A finite-state switch is often the simplest Go representation; separate state types help when each state owns substantial behavior. Write allowed transitions down so invalid combinations cannot quietly become normal.

02

A complete Go example

Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.

go
package main

import (
	"errors"
	"fmt"
)

type Phase int

const (
	Draft Phase = iota
	Submitted
	Approved
)

type Review struct{ phase Phase }

func (r *Review) Submit() error {
	if r.phase != Draft {
		return errors.New("not a draft")
	}
	r.phase = Submitted
	return nil
}
func (r *Review) Approve() error {
	if r.phase != Submitted {
		return errors.New("not submitted")
	}
	r.phase = Approved
	return nil
}

func main() {
	r := &Review{}
	fmt.Println(r.Approve())
	fmt.Println(r.Submit())
	fmt.Println(r.Approve())
}

// Output:
// not submitted
// <nil>
// <nil>
Read the code

Approve rejects a transition from Draft, then succeeds after Submit. One enum avoids contradictory submitted and approved booleans. This demonstrates the state-machine intent; the classic object State pattern delegates to separate state objects when their behaviors justify it.

03

Tradeoffs and the simpler alternative

Persisted state machines need concurrency control, transition audit and idempotency. Do not create a type for each enum value when a short switch is clear. State varies because lifecycle changes; Strategy is typically selected as an interchangeable policy by a caller.

TAKE THIS WITH YOU

Restricts behavior to valid transitions from current state. Persisted state machines need concurrency control, transition audit and idempotency. Do not create a type for each enum value when a short switch is clear. State varies because lifecycle changes; Strategy is typically selected as an interchangeable policy by a caller.

Check your understanding

Which statement best describes State: behavior follows a valid lifecycle?

Go to the source

Progress stays in this browser. No account needed.