Have you ever copied a code sample from a tutorial, pasted it into your editor, and hit an immediate error?
I have, and it is a frustrating experience especially when it's not clear what I need to supply. It generally looks something like this:
# TODO(developer): Uncomment and set your instance
# instance_id = "your-instance-id"
client = storage.Client(instance_id)
In technical writing, this is called the copy-paste-completable pattern. When authors write samples, the default instinct is to reach for a TODO(developer) comment to show where user inputs belong.
I believe we need to break this habit.
Instead of handing developers incomplete code that requires manual assembly, we should provide code that is copy-paste-callable (a clean function with explicit parameters) or copy-paste-runnable (a standalone script with safe fallbacks).
As people increasingly rely on AI assistants and automated agents to build applications, these placeholder comments cause churn and friction. When an AI agent digests a sample containing TODO placeholders, it may reproduce those exact commented-out lines in the generated code or, worse, fill them in with hallucinated code. The human then has to manually debug and clean up incomplete code blocks that should have worked out of the box.
In our work maintaining code samples across ecosystems, my team has wrestled with this exact tension firsthand. The way we author samples directly shapes how developers, test suites, and AI tools interact with our platforms. Here is my perspective on why the traditional TODO comment is a developer experience bottleneck, and how we can transition our sample code to a model of Executable Contracts.
| Architectural Dimension | The Habit: TODO
|
My Goal: Executable Contracts |
|---|---|---|
| First Run Experience | Fails with syntax or scope errors | Runs successfully or fails with clear API auth errors |
| Input Definition | Hidden inside comments | Declared as explicit parameters or env variables |
| Testing in CI | Requires complex preprocessors to uncomment lines | Tested with standard unit test frameworks |
| Agent / AI Reliability | Causes agents to output incomplete, broken stubs | Ensures agents generate clean, production-ready functions |
Why Do We Reach for TODO?
To break a habit, we have to understand why we started doing it in the first place. As sample authors, we often carry deep context on the product we're explaining. We know the setup steps, the security roles, and the resource identifiers. Because the environment feels second nature to us, we drop a TODO(developer) comment and assume the next step is obvious. But a placeholder comment offloads the cognitive burden onto the reader.
Forcing ourselves to eliminate TODO comments early changes this dynamic. It compels us as authors to confront that friction head-on while drafting the sample. When you cannot hide behind a TODO, you have to answer essential questions:
- How will the caller run this function?
- What values make sense as safe defaults?
- What prerequisite setup must be documented upfront?
Addressing these questions early ensures we provide sufficient context before the code ever reaches an end user.
When authors write code samples, they usually encounter three distinct situations where TODO comments feel like the natural choice:
| Situation | Why authors use TODO
|
Architectural Requirement |
|---|---|---|
| Function-scoped snippets (API samples & standard snippets) | A value varies by environment or requires prerequisite setup steps to derive, such as a KMS key or resource URI. | Needs an explicit function parameter with clear docstrings, separating environmental inputs from sample business logic. |
| Global-scoped executable code (Top-level Python/JS scripts, notebooks) | There is no function signature to accept arguments. | Needs top-level variable initialization using universal environment variables and safe fallback defaults. |
| Behavioral configuration & declarative files (Request options, SQL, Terraform) | The author wants to show a configurable setting or needs a resource qualifier where arguments and env vars cannot reach. | Needs predictable example-literals or direct references to real, public, read-only fixtures. |
The Real Cost of the TODO Habit on Developer Experience
Reaching for TODO is easy, but it creates compounding friction for both human developers and the automated tools they use.
Erodes Developer Trust on Step One
When code requires manual edits to run, the sample experience is fundamentally broken.
Beyond the immediate syntax error, broken samples create an emotional barrier. Every manual step we force developers to take before their first successful execution adds cognitive load and erodes trust in the SDK. Developers should experience a working run before worrying about local configuration details.
Shadows the True Parameter Contract
A function signature is a contract. Parameters communicate what you must supply, while inline literals communicate the specific configuration.
TODO comments break this contract. Authors often place TODO comments inside function bodies to tell readers to uncomment local variables. This leads to variable shadowing bugs where uncommented local variables override the function parameters passed in by caller code.
Multiplies Churn for Users of AI Agents
Many developers now use AI agents to generate whole files or integrate services. Because legacy codebases are filled with TODO(developer): Uncomment these variables, models have trained on this pattern. When an agent generates code based on these templates, it frequently leaves variable declarations commented out. The user is then left with a broken file and must spend time diagnosing why their AI assistant produced incomplete stubs.
Worse, in autonomous workflows, an agent might attempt to execute the sample, hit the missing variable, and fail. When the agent cannot resolve the missing ambient context, it exhausts its retry budget and quietly gives up, potentially routing around your platform entirely in favor of an alternative solution.
As AI agents increasingly execute code directly, samples must be safe by default. Using an example- prefix (such as example-bucket or example-dataset) acts as a blast shield. It guarantees that if an agent executes code blindly against a live project, it fails safely or creates a harmless throw-away resource, rather than mutating production data.
Breaks Automated Tooling (CI and Linters)
Testing commented-out code in CI is difficult. To test these snippets, our build systems often use complex test wrappers that inject variables into the scope before executing the file. This workaround only works in dynamic languages like Python and Ruby. It fails completely in compiled or strictly-scoped environments:
- Go, Java, and C# do not permit you to inject scope into arbitrary source blocks.
- Modern JavaScript (ESM) disallows runtime scope injection around top-level imports.
Teams end up maintaining regex scripts in CI pipelines just to uncomment code before testing.
Furthermore, in standard software engineering, a TODO is a lint failure. It is a deterministic signal that a pull request is unfinished. When we allow TODO(developer) in our public docs, linters can't distinguish between an unfinished draft and an intentional instructional placeholder. Removing TODO completely restores our static analysis tools to binary gates and allows CI to test code with standard unit test frameworks.
Where I Want Us to Go
Breaking a habit requires offering a better path forward. Here is how I want us to design our samples.
Default to Parameterized Functions
Instead of global scripts with commented-out variables, wrap your logic in a function. Use standard programming patterns to define inputs.
Do not do this (copy-paste-completable):
# TODO(developer): Uncomment and set your project ID
# project_id = "your-project-id"
# location = "your-location"
def create_cluster():
client = container.ClusterManagerClient()
parent = f"projects/{project_id}/locations/{location}"
Instead, do this (copy-paste-callable):
def create_cluster(project_id: str, location: str = "us-central1" ) -> None:
client = container.ClusterManagerClient()
parent = f"projects/{project_id}/locations/{location}"
Use Environment Variables with Fallbacks (copy-paste-runnable)
If your sample must run as a standalone script, fetch environment variables with fallback defaults.
import os
import warnings
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT", "example-project-id")
if project_id == "example-project-id":
warnings.warn("Using default 'example-project-id'. Set the GOOGLE_CLOUD_PROJECT environment variable to use your own.")
When we introduce standard constructs like environment variables, we rely on universal language features. Even if a developer is encountering them for the first time, these are standard patterns they can easily reason about (and crucially, that their AI agents understand perfectly).
The actual hurdles we throw in a developer's way are not these standard language features. The real blockers are the proprietary, platform-specific givens and setup steps that we expect them to guess. Replacing TODO comments with standard patterns translates our platform's internal context into a universal language.
Provide Real Fixtures
Whenever possible, don't just leave the user guessing how to provision the prerequisite resources. Provide public, read-only fixtures—like a globally readable database table or a public storage bucket—that the sample targets by default.
Showing the builder how to get to a successful "Hello World" state using known-to-exist resources builds immediate momentum and keeps them in the flow of learning, rather than forcing them to detour into infrastructure provisioning.
Enforce the Standard via Automation
We need to back up our design standards with tooling. To keep our codebases from regressing into legacy habits, deploy automated checks that fail any commit containing TODO in a sample folder. This turns our style guidelines into automated, objective gates.
Let's Change the Habit
Writing a TODO(developer) comment is an easy shortcut when writing documentation, but the downstream costs to our developer experience and our AI tooling are too high.
I want us to commit to writing code that is clean, testable, and immediately runnable. Let's talk about sample code architecture openly as an industry. When we treat sample code with the same engineering rigor as production libraries, everyone benefits.
What are your thoughts on this approach? Have you noticed AI agents reproducing these broken TODO patterns in your own development workflows? Let me know in the comments below!
- Header illustration generated by Google Gemini.
Special thanks to Nim Jayawardena for reviewing early drafts of this post.
Thank you to Adam Ross for his collaborative leadership and sample talk-time in helping improve developer experience. Our recent discussions on whether TODOS are acceptable in some sample code is exactly what inspired this post.
Top comments (4)
The parameter contract framing is the strongest point here, because a signature tells me what I owe the code before I run it. I would add one exception for teaching samples: a named placeholder can help when the point is the structure, not the call. How do you keep that teaching value without letting the placeholder survive into copy pasted code?
Great question!
That’s exactly the tension we try to balance! To keep the teaching value without leaving landmines, our approach is to use syntactically valid placeholders. This means the code won't break structurally if a user uses it as expected.
We only let the code fail in common areas where we literally cannot provide a working value for them, such as a project_id. We assume users already know they need a project ID to use the platform, so we don't try to teach that concept in the sample itself. By making the placeholder a valid string (like "example-project-id"), if it does accidentally survive into production, it fails gracefully at runtime with a clear authorization or missing resource error, rather than a confusing compile-time issue.
The obsession with wrapping remote APIs into multi-layered agent abstractions is creating massive hidden latency and failure cascades in production.
If you profile real-world workloads, bare-metal local inference (via direct C++ / llama.cpp or vLLM bindings) consistently beats fragile cloud wrappers on both predictability and cost. Tooling should eliminate abstraction penalties, not compound them.
That’s a really interesting perspective on production AI architectures. I think your point about 'hidden latency and failure cascades' highlights why we need to be so careful with our code.
When the underlying systems are already that complex and prone to abstraction penalties, the last thing we need is sample code full of broken TODOs making it even harder to debug. Thanks for sharing.