A ticket is not a command to start typing. It is a small statement of a desired outcome inside a much larger system of dependencies, people, and risk.
A feature is not finished because code exists on your laptop. It is finished when the intended behaviour has been delivered, verified, and made operable for the people who depend on it—according to the team’s definition of done.
Let’s follow a hypothetical change: a checkout service should allow customers to safely retry a request after a network timeout without accidentally creating a duplicate order.
Stage 1: Turn the ticket into an observable outcome
Before choosing a design, clarify the problem.
Questions to resolve:
- What does the user currently experience?
- What should happen instead?
- How will we know the behaviour is correct?
- What must not change?
- Are there security, data, latency, or compatibility constraints?
Turn the answer into acceptance criteria. For example: “A retry using the same idempotency key returns the same logical order outcome rather than creating another order.” This is still only an example; the real contract must match the product’s payment and order semantics.
If a requirement is ambiguous, ask early. A short clarification can prevent a large, beautifully implemented misunderstanding.
Stage 2: Explore and plan the smallest safe change
Read the relevant code, tests, project instructions and recent history. Map the request path. Identify the owner of the invariant and the boundaries that need to agree.
Write a plan that includes:
- The expected files or layers to change.
- The behaviour you will preserve.
- The tests that will prove the change.
- Any migration, configuration, or rollout concern.
- The risks that need review.
If you use an AI coding assistant, ask it to investigate before editing on unfamiliar or risky tasks. Review its plan; the model does not own the consequences.
Stage 3: Implement a reviewable slice
Prefer a coherent patch over a sweeping redesign. Follow existing conventions unless they are part of the problem. Add tests with the implementation, not as an afterthought.
Keep your working tree understandable:
git status --short
git diff --check
git diff --stat
These commands do not prove the implementation is correct. They help you notice accidental files, whitespace errors, and changes larger than expected. Read the actual diff before staging or committing.
If the task reveals a necessary architectural change, explain why it is needed and revisit the plan rather than quietly expanding the scope.
Stage 4: Verify at multiple levels
Verification should follow the risk and the shape of the change. A typical sequence is:
- Focused unit or component tests.
- Integration tests that cover important boundaries.
- Type checks, linting, and the project build.
- A manual or automated end-to-end path where practical.
- A review of security, failure handling, and data integrity.
Test both the intended path and the meaningful ways it can fail. A test suite that is green but does not exercise the changed contract gives limited confidence.
Capture what actually ran. Do not report a command as passing if it was skipped or if the output was ambiguous.
Stage 5: Open a pull request that tells the story
A useful pull request makes the reviewer faster. Include:
- Why: the user or system problem.
- What: the implementation and any important design decision.
- Verification: checks run and manual steps performed.
- Risk: migration, rollout, compatibility, or operational concerns.
- Follow-up: anything intentionally left out.
Link the task and provide screenshots or traces when they genuinely help. Keep the explanation honest; do not describe planned tests as completed tests.
Then respond to feedback. A review comment is not a personal verdict. It is additional information about correctness, maintainability, team standards, or risk.
Stage 6: Release deliberately
The deployment process depends on the product. A static site, a mobile app, and a payment service do not need identical release controls. The general questions are similar, however:
- Can the change be deployed without breaking old and new versions during transition?
- Does it require a database migration or a configuration update?
- Is there a canary, feature flag, or staged rollout appropriate to the risk?
- How will we detect a regression?
- What is the rollback or mitigation plan?
- Who owns the release if it misbehaves?
For a small low-risk change, the answers may be simple. For a high-impact data migration, they may require explicit sequencing and coordination.
Stage 7: Observe the outcome
After release, check the system’s behaviour—not only the deployment status. Look at relevant error rates, latency, logs, business metrics, and user feedback. Compare with the expected outcome and the baseline.
If the release fails, stabilize the system first, communicate clearly, and follow the team’s incident process. Do not hide a serious issue to protect the appearance of a successful deployment.
When the situation is stable, capture what was learned. If the root cause was a missing test, weak monitoring, unclear ownership, or an unsafe rollout, improve that part of the system where it is worth the cost.
What “done” really means
A reasonable definition of done for a change might include:
- The acceptance criteria are met.
- The code is understood by its owner and reviewed appropriately.
- Relevant tests and checks have passed.
- Documentation and operational details are updated where needed.
- The change is deployed—or its release status is explicit.
- The result is observed, and remaining risks have an owner.
Not every team uses these exact gates. The important thing is that completion is explicit rather than assumed.
The whole lifecycle in one view
Clarify → Explore → Plan → Implement → Verify
↓
Learn ← Observe ← Release ← Review ←───┘
Engineering is not simply producing code. It is moving from an uncertain need to a reliable outcome while managing risk at every step.
The code change is one part of the work. The delivered behaviour is the result.