<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm" -->

---
title: How the controller-runtime Cache Actually Works, and Why...
description: A deep dive into how controller-runtime's cache works internally for Kubernetes controller authors. The cache is built on client-go primitives — Reflector,...
canonical: https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: How the controller-runtime Cache Actually Works, and Why Your Controller Does Not Crash the API Server | daily.dev
og:description: A deep dive into how controller-runtime's cache works internally for Kubernetes controller authors. The cache is built on client-go primitives — Reflector,...
og:url: https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm
og:image: https://api.daily.dev/og/posts/ZX4UNDfxm.png
og:image:alt: How the controller-runtime Cache Actually Works, and Why Your Controller Does Not Crash the API Server
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# How the controller-runtime Cache Actually Works, and Why Your Controller Does Not Crash the API Server

**[Kubernetes](https://daily.dev/sources/k8s)** · 32 min read · 0 upvotes · 0 comments

## Summary

A deep dive into how controller-runtime's cache works internally for Kubernetes controller authors. The cache is built on client-go primitives — Reflector, DeltaFIFO, and Indexer — and is the foundational operating model, not an optimization. Key insights: r.Get() and r.List() read from an in-memory store populated via list+watch, never hitting the API server directly; writes go straight to the API server; and the cache is fully warmed before the first Reconcile runs. The post covers common mistakes (expecting read-after-write consistency, mutating shared objects in predicates/handlers, misunderstanding resync vs relist), how to use IndexField for O(1) lookups instead of O(n) scans, selective caching with namespace/label/field selectors and Transform to reduce memory, PartialObjectMetadata for metadata-only watches, and when to use APIReader for guaranteed freshness.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://kubernetes.io/blog/2026/07/29/controller-runtime-cache-explained>

## Questions this post answers

### Does calling r.Get() inside a Kubernetes controller-runtime Reconcile function send a request to the API server?

No, r.Get() reads from a local in-memory cache populated by list and watch, not from the API server. The cache is backed by a plain map keyed by namespace/name, so a cache hit costs only a map lookup and a DeepCopy of the object, taking microseconds with no HTTP, TLS, or etcd involvement.

_Developers debugging controller read behavior can dig into these caching internals through daily.dev._

### Why does re-reading a Kubernetes object right after calling Update() in a controller-runtime reconciler show the old value?

Between executing Update and the cache reflecting the new state there is a short asynchronous window, typically milliseconds, because writes go directly to the API server while reads come from a watch-fed cache that has not yet caught up. Reconcile logic should be written to be idempotent rather than relying on immediate read-after-write consistency; APIReader can be used when guaranteed freshness is required.

_Teams hardening reconciler logic against stale reads can track these consistency gotchas via daily.dev._

### How do I avoid an O(n) scan when listing Kubernetes objects by a specific field in a controller-runtime controller?

Register a field index with mgr.GetFieldIndexer().IndexField before startup, then query with client.MatchingFields using that same key. The indexer maintains an inverted dictionary that is updated incrementally as objects change, so List with MatchingFields performs a direct key lookup instead of scanning every cached object; without a registered IndexField, controller-runtime rejects the MatchingFields query.

_Engineers optimizing controller performance at scale can follow indexing patterns like this on daily.dev._

## Similar posts on daily.dev

- [Unlocking efficiency: A guide to operator cache configuration on Red Hat OpenShift and Kubernetes](https://daily.dev/posts/unlocking-efficiency-a-guide-to-operator-cache-configuration-on-red-hat-openshift-and-kubernetes-rrynupdxu) · Red Hat Developer · 0 upvotes · 0 comments
- [5 anti-patterns that cause Kubernetes operator vulnerabilities](https://daily.dev/posts/5-anti-patterns-that-cause-kubernetes-operator-vulnerabilities-tjsfjyvka) · Red Hat Developer · 6 upvotes · 0 comments
- [Issue \#607: Comparing six Go cache designs — Go Weekly](https://daily.dev/posts/issue-607-comparing-six-go-cache-designs-go-weekly-fsv6lgymj) · Golang Weekly · 8 upvotes · 0 comments
- [Inside the Kubernetes API Server: The Brain Behind Every Kubernetes Operation](https://daily.dev/posts/inside-the-kubernetes-api-server-the-brain-behind-every-kubernetes-operation-dmpyolvtj) · Medium · 0 upvotes · 0 comments

---

Tags: [#kubernetes](https://daily.dev/tags/kubernetes), [#golang](https://daily.dev/tags/golang)

[View this post on daily.dev](https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"How the controller-runtime Cache Actually Works, and Why Your Controller Does Not Crash the API Server","url":"https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm"},"datePublished":"2026-07-29T23:54:12.561Z","dateModified":"2026-09-14T08:43:03.579Z","description":"A deep dive into how controller-runtime's cache works internally for Kubernetes controller authors. The cache is built on client-go primitives — Reflector,...","image":"https://media.daily.dev/image/upload/s--P4t4XyoV--/f_auto/v1722860399/public/Placeholder%2001","thumbnailUrl":"https://media.daily.dev/image/upload/s--P4t4XyoV--/f_auto/v1722860399/public/Placeholder%2001","isAccessibleForFree":true,"articleSection":"Kubernetes","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Kubernetes","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/k8s","url":"https://daily.dev/sources/k8s"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"kubernetes,golang","timeRequired":"PT32M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Kubernetes","item":"https://daily.dev/sources/k8s"},{"@type":"ListItem","position":3,"name":"How the controller-runtime Cache Actually Works, and Why Your Controller Does Not Crash the API Server"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/how-the-controller-runtime-cache-actually-works-and-why-your-controller-does-not-crash-the-api-serv-zx4undfxm#faq","mainEntity":[{"@type":"Question","name":"Does calling r.Get() inside a Kubernetes controller-runtime Reconcile function send a request to the API server?","acceptedAnswer":{"@type":"Answer","text":"No, r.Get() reads from a local in-memory cache populated by list and watch, not from the API server. The cache is backed by a plain map keyed by namespace/name, so a cache hit costs only a map lookup and a DeepCopy of the object, taking microseconds with no HTTP, TLS, or etcd involvement. Developers debugging controller read behavior can dig into these caching internals through daily.dev."}},{"@type":"Question","name":"Why does re-reading a Kubernetes object right after calling Update() in a controller-runtime reconciler show the old value?","acceptedAnswer":{"@type":"Answer","text":"Between executing Update and the cache reflecting the new state there is a short asynchronous window, typically milliseconds, because writes go directly to the API server while reads come from a watch-fed cache that has not yet caught up. Reconcile logic should be written to be idempotent rather than relying on immediate read-after-write consistency; APIReader can be used when guaranteed freshness is required. Teams hardening reconciler logic against stale reads can track these consistency gotchas via daily.dev."}},{"@type":"Question","name":"How do I avoid an O(n) scan when listing Kubernetes objects by a specific field in a controller-runtime controller?","acceptedAnswer":{"@type":"Answer","text":"Register a field index with mgr.GetFieldIndexer().IndexField before startup, then query with client.MatchingFields using that same key. The indexer maintains an inverted dictionary that is updated incrementally as objects change, so List with MatchingFields performs a direct key lookup instead of scanning every cached object; without a registered IndexField, controller-runtime rejects the MatchingFields query. Engineers optimizing controller performance at scale can follow indexing patterns like this on daily.dev."}}]}
```

