RFC #
Every engineer has faced this situation: the system is built, running in production, but nobody can explain why a certain architecture decision was made. Or worse — a major change was implemented without communicating with other teams, and the impact only surfaced weeks later as a production incident. The RFC (Request for Comments) exists as the answer to that problem. It isn’t a bureaucratic document — it’s a communication and technical decision-making tool that forces engineers to think deeper before writing a single line of code.
What Is an RFC? #
In software engineering, an RFC is a formal document used to propose, discuss, and document significant technical changes before those changes are implemented.
The term RFC originally became popular in the internet standards world (IETF) — almost every internet protocol we use today was born from the RFC process. In modern engineering, companies like Google, Meta, Netflix, and Stripe have adapted it as a structured engineering decision record for internal decisions.
An RFC isn’t just the final documentation of an already-made decision — it’s a tool for making better decisions. The difference matters:
// ✗ RFC as retroactive documentation
Decision made quietly → implementation starts → RFC written "for the archive"
→ No meaningful review
→ The RFC is just a formality
// ✓ RFC as a decision-making tool
Idea emerges → RFC written → open discussion → decision made together → implementation starts
→ Review happens at the right stage, before sunk costs make decisions hard to change
→ All stakeholders share the same context
RFCs can cover various types of significant technical changes: system architecture changes, adding or removing services, API contract changes, database schema changes with wide impact, and technology or library selection decisions.
Why Are RFCs Important? #
Technical Changes Always Have Wider Impact Than They Appear #
Engineers writing code tend to see impact from their own viewpoint. A change that looks small from one point can significantly affect other systems:
flowchart TD
A[Change: switch API response format] --> B[Service A — needs parser updates]
A --> C[Service B — needs model updates]
A --> D[Mobile App — needs contract updates]
A --> E[Monitoring — alert rules change]
A --> F[Documentation — needs updating]
A --> G[QA Automation — test cases need adjusting]An RFC forces engineers to think end-to-end about a change’s impact — not just from the perspective of the service being worked on.
Avoiding Hero Engineering #
Hero engineering is the pattern where one engineer (usually the most senior or experienced) makes big technical decisions unilaterally, implements them, then asks for review after the code is already done.
// ✗ The Hero Engineering pattern
Senior Engineer makes architecture decisions alone
→ Implements directly without discussion
→ PR opened after 2 weeks of coding
→ "Please review, I want to merge tomorrow because of the deadline"
→ Other teams have no context → review is just a formality
→ The decision can't be changed because it's already too expensive
// ✓ The RFC-driven pattern
An engineer writes an RFC — even juniors can
→ The RFC is opened for comments from the whole team
→ Discussion happens at the idea stage, not the code stage
→ The final decision is more robust because it passed through many viewpoints
→ Everyone relevant shares the same context
RFCs level the playing field — junior engineers can give meaningful feedback on architecture decisions, and senior engineers can no longer make big decisions without scrutiny.
Reducing Rework Costs #
The later a problem is discovered, the more expensive it is to fix. RFCs move discussion to the earliest phase — before any code is written.
flowchart LR
A["Idea Phase\nRFC"] --> B[Design Phase]
B --> C[Implementation Phase]
C --> D[Testing Phase]
D --> E[Production]
A -.->|"Change cost: low\n(edit a document)"| A
C -.->|"Change cost: high\n(refactor code)"| C
E -.->|"Change cost: very high\n(incident + hotfix + migration)"| EIt’s cheaper to discard an idea at the document phase than to discard code after production.
Being Institutional Memory #
Six months after implementation, nobody remembers why a certain decision was made. With a well-preserved RFC, the question “why was this system built this way?” can always be answered:
Without an RFC:
New engineer: "Why do we use Redis for sessions instead of a database?"
Senior: "Hmm... I forgot, maybe there was a performance problem back then?"
→ The decision can't be objectively evaluated
With an RFC:
New engineer: "Why do we use Redis for sessions instead of a database?"
→ RFC-007: Session Storage Migration (2023-Q2)
→ Background: database sessions caused query spikes during high traffic
→ Alternatives considered: sticky sessions, JWT, Redis
→ Decision: Redis chosen for native TTL and cluster support
→ The decision can be evaluated: still relevant? have conditions changed?
When Should an RFC Be Written? #
Not every change needs an RFC. Writing RFCs for every small change produces counterproductive overhead. Use this guide:
flowchart TD
A{"Does this change\naffect more than\none service?"} -- Yes --> RFC
A -- No --> B{"Does it change an\nAPI contract or\ndata model?"}
B -- Yes --> RFC
B -- No --> C{"Is it hard to\nroll back if\nit fails?"}
C -- Yes --> RFC
C -- No --> D{"Does it affect\nperformance,\ncost, or security?"}
D -- Yes --> RFC
D -- No --> E["A PR + comments\nat code review is enough"]
RFC[Write an RFC]A simple rule of thumb: if a change can’t be fully explained in a 5-minute sync, it probably needs an RFC.
| Needs an RFC | Doesn’t Need an RFC |
|---|---|
| Migrating from monolith to microservices | Refactoring variable or function names |
| Adding a new message broker | Adding a simple CRUD endpoint |
| Changing the authentication strategy | Fixing an isolated bug |
| Replacing the database engine | Minor dependency updates |
| Designing a centralized caching system | Adding a non-breaking response field |
| Changing the deployment strategy | Changing internal log formats |
The RFC Lifecycle #
An RFC isn’t a static document — it has a lifecycle that must be followed to be effective:
stateDiagram-v2
[*] --> Draft : Engineer writes the RFC
Draft --> InReview : RFC ready for discussion
InReview --> Draft : Significant revisions needed
InReview --> Accepted : Consensus reached
InReview --> Rejected : Not worth continuing
Accepted --> Implemented : Implementation finished
Rejected --> [*]
Implemented --> [*]Each status has different implications:
| Status | Meaning | Required Action |
|---|---|---|
| Draft | Still being written, not ready for review | Author completes the document |
| In Review | Open for comments and discussion | Reviewers give feedback within the deadline |
| Accepted | Decision made, implementation may start | Author starts implementation |
| Rejected | Won’t continue, with clear reasons | Document the rejection reasons |
| Implemented | Implementation finished, RFC becomes a historical reference | Update the RFC with actual results notes |
The Anatomy of a Good RFC #
A good RFC isn’t just a proposal — it’s a document that lets reviewers make decisions with complete information. Here are the sections that must exist:
RFC Header #
# RFC-[NUMBER]: [Short Descriptive Title]
**Status:** Draft | In Review | Accepted | Rejected | Implemented
**Author:** [Name / Team]
**Reviewer:** [Requested reviewer names]
**Date Created:** YYYY-MM-DD
**Date Updated:** YYYY-MM-DD
**Decision Target Date:** YYYY-MM-DD
The RFC number matters for reference — “as decided in RFC-012” is far more traceable than “as we discussed last month”.
Background #
Explain the system’s current condition and the context reviewers need to understand the problem. Reviewers unfamiliar with the domain must be able to understand the situation from this section.
Example of good background:
"Currently the Excel file upload process for product imports is processed synchronously
at the POST /api/products/import endpoint. Data shows an average processing time of
3-5 minutes for files with 1,000 rows, and 15-20 minutes for 10,000-row files.
In the last 30 days, there were 47 user-reported timeout errors (12% rate)."
// ✗ Useless background
"Our system has a performance problem that needs fixing."
Problem Statement #
Write the problem explicitly and measurably. A good problem statement answers three questions:
1. What's wrong or suboptimal?
"The product import endpoint times out for files >5,000 rows"
2. What's the impact and how big is it?
"12% of requests fail, users must re-upload, CS receives ~15 tickets/week"
3. Who is affected?
"Merchants with large catalogs (>1,000 SKUs) — the most valuable segment"
Goals and Non-Goals #
This section is often skipped but is very important for preventing scope creep during discussion.
**Goals:**
- Eliminate timeouts in the product import process for all file sizes
- Give users progress feedback during the process
- Ensure imports can resume after partial failures
**Non-Goals (outside this RFC's scope):**
- Optimizing database performance for product queries (will be discussed in a separate RFC)
- More advanced file format validation (separate backlog item)
- Real-time notifications via WebSocket (could be a future enhancement)
Proposed Solution #
Explain the proposed solution at the architecture level — not code implementation details. Focus on how the system works, not how the code is written.
sequenceDiagram
participant Client
participant API as API Gateway
participant Worker as Background Worker
participant Queue as Message Queue
participant DB
Client->>API: POST /import (file)
API->>Queue: Enqueue job
API-->>Client: 202 Accepted + job_id
Queue->>Worker: Dequeue job
Worker->>DB: Process & save rows
Worker->>DB: Update job status
Client->>API: GET /import/{job_id}/status
API->>DB: Query job status
API-->>Client: {status, progress, errors}Alternatives Considered #
This is the most often skipped section and the one that most shows an RFC’s quality. Listing alternatives — and the reasons they weren’t chosen — proves the decision was made consciously, not because of limited knowledge.
Alternative 1: Chunked Upload + Synchronous Processing
Pros: Simpler implementation, no new infrastructure needed
Cons: Still has a time limit per chunk, unsuitable for very large files
Why not chosen: Only moves the timeout problem, doesn't solve it
Alternative 2: Streaming Processing via WebSocket
Pros: Real-time feedback, better UX
Cons: High complexity, needs major client changes
Why not chosen: Over-engineering for current needs; could become a
future enhancement once async processing is stable
Alternative 3: Background Job with Polling (Proposed)
Pros: Simple, proven pattern, no persistent connection needed
Cons: Slight delay in status updates (polling interval)
Why chosen: The best trade-off between complexity and value
Impact and Risks #
This section must be written honestly — including negative impacts and failure scenarios.
Positive Impacts:
✓ Timeout errors completely eliminated
✓ File capacity unlimited (within storage limits)
✓ Server no longer blocked during the import process
Impacts to Note:
! New infrastructure: needs a message queue (Redis/RabbitMQ)
! Increased operational complexity (worker monitoring)
! User experience change: from synchronous to polling
Failure Scenarios:
✗ Worker crashes mid-process: partial imports saved in the DB
Mitigation: Per-row idempotency keys, imports can be resumed
✗ Message queue full: new requests temporarily rejected
Mitigation: Queue size monitoring + alerting + graceful rejection with clear messages
✗ Jobs stuck too long in the queue: users wait too long
Mitigation: Priority queue for small files, per-job deadlines
Migration and Rollback Plan #
The most often forgotten but most critical section for decisions affecting running systems.
Migration Plan:
1. Deploy the message queue infrastructure (doesn't affect the current system)
2. Deploy the worker service in "shadow" mode — processes but doesn't persist
3. Run in parallel: the old endpoint stays active, the new /v2/import endpoint is available
4. Gradual rollout: 10% → 50% → 100% of traffic to the new endpoint
5. Monitor error rates and processing times for 1 week
6. Deprecate the old endpoint after 2 sprints
Rollback Plan:
If problems occur after rollout:
- Traffic can return to the old endpoint with a feature flag (< 5 minutes)
- The worker can be shut down without affecting the old endpoint
- Jobs already in the queue can be drained or discarded
- No non-backward-compatible database schema changes
Open Questions #
A good RFC doesn’t have to be perfect at the start. Listing unanswered questions actually helps reviewers focus on areas needing input:
1. Do we need to keep import history for audit trails?
→ Needs input from Product and Compliance
2. How long should job results be kept before cleanup?
→ Trade-off between storage cost and users' need to re-download results
3. Does the worker need autoscaling based on queue depth?
→ Needs discussion with DevOps about cost implications
4. How to handle invalid row data — stop the entire import or skip and continue?
→ Needs a Product Owner decision
RFC Writing Best Practices #
Write the RFC Earlier Than You Think You Need To #
RFCs are most effective when written while the idea is still raw — before you’re too invested in a particular solution. An RFC written after 2 weeks of implementation isn’t an RFC; it’s post-hoc rationalization.
// ✗ Too late
"I've finished implementing async processing. I'll write the RFC now."
→ Review is meaningless because the change cost is already high
// ✓ Right timing
"I have an idea for async processing. Before I start, I'll write an RFC first."
→ Review can change the approach before any code is written
Focus on the “Why”, Not the “How” #
Implementation details will evolve during development. But the reasons behind architecture decisions rarely change. An RFC too focused on code details becomes outdated quickly.
// ✗ Too implementation-focused
"The worker will use goroutines with a channel buffer size of 100 and
a 30-second context timeout per batch..."
// ✓ Architecture-decision-focused
"The import process will be separated from the request lifecycle using the
background job pattern, letting the server respond immediately without waiting
for the process to finish. Implementation details (goroutines vs thread pools,
queue library) will be decided during implementation based on actual load characteristics."
Keep the RFC Length Reasonable #
An RFC that’s too long won’t be read carefully. An RFC that’s too short doesn’t provide enough context for good decisions.
RFC length targets:
✓ 2-6 pages for a standard RFC
✓ Readable and understandable in 15-30 minutes
✓ Diagrams preferred over long paragraphs
Signs an RFC is too long:
✗ More than 10 pages for a non-revolutionary change
✗ Explaining things the audience should already understand
✗ Including code details that belong in a PR
Set Review Deadlines #
RFCs without deadlines tend not to get timely reviews — everyone procrastinates because they feel “there’s still time”.
// ✓ Good practice
RFC-012 opened for review
Decision target: 2026-06-20 (7 working days)
Requested reviewers: @developer-a, @tech-lead-b, @platform-team
If there are no significant objections by the deadline, the RFC is considered Accepted
// ✗ No clear structure
"Please review it when you have time"
→ Nobody feels responsible
→ The RFC languishes for weeks
Update the RFC After Implementation #
An implemented RFC that isn’t updated loses its function as institutional memory. Add actual results notes — what went as planned, what differed, and what was learned.
## Post-Implementation Notes (added after the RFC was implemented)
**Implementation Date:** 2026-05-15
**Final Status:** Implemented
**Actual Results:**
- Timeout errors dropped from 12% to 0.1% (target: 0%)
- Processing time for 10,000-row files: 45 seconds average (initial estimate: 60 seconds)
- Highest queue depth during peak: 23 jobs (no issues)
**What Differed from the Plan:**
- Polling interval raised from 5 seconds to 10 seconds based on UX feedback
- Redis chosen as the queue (instead of RabbitMQ) because it was already in the infrastructure
**Lessons:**
- Per-row idempotency keys proved critical — 3 partial import cases were successfully resumed
- Worker autoscaling isn't needed at current load, but should be considered
if the merchant base grows 5x
RFC Anti-Patterns to Avoid #
// ✗ RFC as a one-way document
RFC written, sent, implemented immediately without waiting for feedback
→ The RFC is just a formality, not a decision tool
// ✓ RFCs must wait for feedback before implementation starts
Set a review deadline and honor the process
// ✗ RFCs that are too technical and detailed
Explaining every line of code in the RFC document
→ Reviewers drown in details, lose the big picture
// ✓ RFCs focus on architecture and decisions, not implementation
// ✗ RFCs without a clear problem statement
"We need to improve our system"
→ Reviewers don't know what's being solved
// ✓ The problem statement must be specific, measurable, and explain the impact
// ✗ Not listing the alternatives considered
"This is the only possible solution"
→ Shows incomplete analysis
// ✓ Always consider at least 2 alternatives, even if one is "do nothing"
// ✗ RFCs without a rollback plan
"We can roll back if there are problems"
→ How? How long? What's the impact?
// ✓ Rollback plans must be concrete: steps, time estimates, data impact
Pre-Publication RFC Checklist #
DOCUMENT COMPLETENESS:
□ RFC number and title set
□ Status set to "Draft" or "In Review"
□ Author and reviewer list filled in
□ Decision target date set
CONTENT:
□ Background provides enough context for unfamiliar reviewers
□ Problem statement specific and measurable
□ Goals and Non-Goals clear (prevents scope creep)
□ Proposal explained at the architecture level, not code detail
□ Diagrams included to explain flows or architecture
□ At least 2 alternatives listed with reasons they weren't chosen
□ Positive AND negative impacts written honestly
□ Failure scenarios and mitigations thought through
□ Migration plan concrete and staged
□ Rollback plan specific (steps, time, impact)
□ Open questions written for unanswered questions
PROCESS:
□ RFC shared with the right reviewers (those with context and authority)
□ Review deadline clearly communicated
□ A clear discussion channel exists (document comments, Slack thread, etc.)
Summary #
- RFCs aren’t bureaucracy — they’re decision tools — the goal is producing better decisions, not producing documents.
- Write the RFC before writing code — the earlier feedback arrives, the cheaper the change. An RFC written after implementation isn’t an RFC; it’s post-hoc rationalization.
- Focus on the “why”, not the “how” — implementation details change; the reasons behind decisions don’t.
- Alternatives considered are the most important section — an RFC without alternatives shows incomplete analysis.
- Rollback plans must be concrete — “we can roll back” isn’t a rollback plan. Write the steps, time, and impact.
- Set review deadlines — RFCs without deadlines won’t get timely attention.
- Update the RFC after implementation — add actual results so the RFC stays useful as a historical reference.
- RFCs are institutional memory — they answer the question “why was this system built this way?” for engineers joining next year.
- Not every change needs an RFC — use the decision tree: if it affects many services, changes a contract, or is hard to roll back, write an RFC.