Skip to main content
The return line at the bottom of a controller’s Reconcile function is the controller-runtime’s dispatch board. It tells controller-runtime whether this request is finished for now, should be retried because something failed, or should be requeued at a specific time. That single decision shapes how noisy, calm, and reliable your operator behaves.
The image is a flow diagram titled "The Return Line Is Dispatch," showing a process from "Reconcile returns" to options including "Done," "Retry," and "Wake later." It includes a note saying, "The choice shapes the operator."
Think of Reconcile as a technician closing a work ticket. At the end of the ticket the technician must leave one clear instruction for dispatch: work completed for now, the work failed and should be retried, or check back at a specific time. In controller code, the ctrl.Result plus the error returned are that instruction — they are not just Go bookkeeping. Key behaviors:
  • An empty result and nil error: work done for now; rely on watches to enqueue the object later.
  • A non-nil error: something truly failed; route the request into a rate-limited retry.
  • RequeueAfter: schedule a timed wake-up without treating the current pass as a failure.
  • Requeue (boolean): request another pass soon but without a precise delay.
Examples and guidance
  • Clean path: no retry requested now. This means the controller has completed what it can; future reconciles should be driven by watches or external triggers:
  • Real operation failure: return a non-nil error so controller-runtime retries with backoff. Use this for API failures, transient client errors, or any write/read failure that should be retried:
Do not use errors to represent normal, in-progress states. For example, a Deployment still rolling out or a status field that hasn’t reached its desired value is not a failure — record that state in status/events and let watches or timed requeues drive the next reconcile.
The image explains that normal waiting is not an error, using examples of "Still rolling out" and "Below desired count," both marked as "Not broken." An arrow indicates "Error for progress."
  • Timed reminders: when you need the controller to wake at a particular time (e.g., poll an external API, renew a certificate before expiry), use RequeueAfter. This requests a scheduled requeue without marking the current pass as failed:
  • Unspecified requeue: set Requeue: true if you want another pass soon but don’t need to specify an exact delay. This is less precise than RequeueAfter:
When to prefer each option
  • If timing matters (you need to check at a specific future time): use ctrl.Result{RequeueAfter: <duration>}, nil.
  • If an operation failed and should be retried: return the error (non-nil err) so controller-runtime handles retry/backoff.
  • If the state is handled for now and future reconciles will be triggered by resource watches: return ctrl.Result{}, nil.
Quick reference table Best practices
  • Report waiting states honestly in Status and Events. If a web app is waiting for replicas, set status to reflect that rather than returning an error to force reconciliation.
  • Prefer watches (owner/field/annotation watches) to trigger reconciles automatically when possible — it’s more efficient and produces cleaner logs and metrics.
  • Use RequeueAfter when timing matters. Avoid returning errors for expected progress, which overloads retries and hides real failures.
Treat the Reconcile return value as part of your operator’s behavior, not as an afterthought. It controls retry noise, API server traffic, log quality, and how quickly users see status settle.
Returning errors for expected, in-progress states (for example, a rolling update) will generate unnecessary retries, increase API traffic, and can obscure real failures. Use status + watches or RequeueAfter instead of errors for normal waiting.
Links and references

Watch Video