Skip to content

Writing Effective Technical RFCs That Drive Alignment

Structure technical RFCs that state the problem, weigh alternatives, propose a solution and align the team on architecture, with reusable templates.

4 min read
A technical RFC document flowing through review stages from draft to accepted with team feedback annotations

Why RFCs Matter

The most expensive mistakes in engineering are architecture decisions made without sufficient input. An RFC—Request for Comments—forces you to think through a proposal completely before writing code, and gives stakeholders a structured way to evaluate and improve it. The document becomes a permanent record of why decisions were made, invaluable when someone asks "why did we build it this way?" two years later.

The RFC Structure

Every RFC answers four questions: What is the problem? What are the options? What do you recommend? What are the risks? Keep the structure consistent so readers know where to find information without reading cover to cover.

markdownmarkdown
# RFC: [Title]
 
**Author:** [Name]
**Status:** Draft | In Review | Accepted | Rejected | Superseded
**Created:** YYYY-MM-DD
**Last Updated:** YYYY-MM-DD
**Reviewers:** [Names/Teams]
**Decision Deadline:** YYYY-MM-DD
 
## Summary
[2-3 sentences. What are you proposing and why?]
 
## Motivation
[What problem are we solving? Include data, metrics, user reports.
Why now? What happens if we do nothing?]
 
## Detailed Design
[The core proposal. Architecture diagrams, API contracts,
data models. Enough detail to evaluate feasibility.]
 
## Alternatives Considered
[At least 2 alternatives. For each: brief description,
pros, cons, and why it was not chosen.]
 
## Risks and Mitigations
[What could go wrong? How will you address each risk?]
 
## Rollout Plan
[How will this be deployed? Feature flag? Migration?
What's the rollback plan?]
 
## Open Questions
[Things you don't know yet. Decisions you want input on.]

Writing the Motivation Section

The motivation section is the most important part. If readers do not understand or agree on the problem, they cannot evaluate the solution. Lead with data.

tstypescript
// ❌ Vague motivation — no data, no urgency
// "Our current caching solution has some issues and we should
//  consider upgrading to something better."
 
// ✅ Data-driven motivation with clear problem statement
// "Our Redis cache hit rate has dropped from 94% to 67% over
//  the past 3 months as our dataset grew from 2M to 8M keys.
//  P99 latency for the /api/products endpoint increased from
//  45ms to 380ms. The current single-node Redis instance uses
//  12GB of its 16GB limit. At current growth rate, we will
//  exceed capacity in 6 weeks.
//
//  Impact: 23% of product page loads now exceed the 500ms SLA.
//  Customer support tickets mentioning 'slow loading' increased
//  40% month-over-month."
 
interface MotivationChecklist {
  hasQuantifiedProblem: boolean;      // Metrics, not feelings
  hasTimelineContext: boolean;         // Why now?
  hasImpactAssessment: boolean;       // What happens if we don't act?
  hasStakeholderContext: boolean;      // Who is affected?
  hasCostOfInaction: boolean;          // What's the cost of doing nothing?
}

Evaluating Alternatives Honestly

List alternatives you genuinely considered. If you dismiss them too quickly, reviewers will question whether you explored the space. Acknowledge the tradeoffs of your own proposal.

tstypescript
interface Alternative {
  name: string;
  description: string;
  pros: string[];
  cons: string[];
  estimatedEffort: string;
  whyNot: string;
}
 
const alternatives: Alternative[] = [
  {
    name: "Redis Cluster",
    description: "Scale current Redis horizontally with cluster mode",
    pros: [
      "Minimal code changes — same client library",
      "Team already familiar with Redis operations",
      "Linear horizontal scaling",
    ],
    cons: [
      "Cross-slot operations not supported",
      "Requires resharding as data grows",
      "Higher operational complexity",
    ],
    estimatedEffort: "2-3 weeks",
    whyNot: "Recommended — see Detailed Design",
  },
  {
    name: "DynamoDB DAX",
    description: "Replace Redis with DynamoDB Accelerator",
    pros: [
      "Fully managed, no operational overhead",
      "Automatic scaling",
      "Strong consistency option",
    ],
    cons: [
      "Significant code rewrite — different API",
      "Vendor lock-in to AWS",
      "Higher per-request cost at our scale",
      "Team has no DynamoDB experience",
    ],
    estimatedEffort: "6-8 weeks",
    whyNot: "Migration cost and vendor lock-in outweigh managed benefits",
  },
  {
    name: "Application-level caching with TTL optimization",
    description: "Keep single Redis, optimize cache keys and TTLs",
    pros: [
      "No infrastructure changes",
      "Immediate improvement possible",
    ],
    cons: [
      "Does not address capacity growth",
      "Temporary fix — revisit in 3-6 months",
    ],
    estimatedEffort: "1 week",
    whyNot: "Band-aid that delays the inevitable scaling work",
  },
];

The Rollout Section

Proposals that ignore rollout are incomplete. Reviewers need to know how the change reaches production and what happens if it goes wrong.

tstypescript
interface RolloutPlan {
  phases: Phase[];
  rollbackPlan: string;
  successCriteria: Metric[];
  monitoringAdditions: string[];
}
 
const rolloutPlan: RolloutPlan = {
  phases: [
    {
      name: "Shadow mode",
      duration: "1 week",
      description:
        "Write to both old and new cache. Read from old. Compare results.",
      successCriteria: "< 0.1% divergence between old and new reads",
    },
    {
      name: "Canary — 5% read traffic",
      duration: "3 days",
      description:
        "Route 5% of cache reads to new cluster. Monitor latency and hit rate.",
      successCriteria: "P99 < 50ms, hit rate > 90%",
    },
    {
      name: "Gradual rollout — 25%, 50%, 100%",
      duration: "1 week",
      description: "Ramp read traffic. Each step holds for 24h before advancing.",
      successCriteria: "No degradation from previous stage",
    },
    {
      name: "Decommission old cache",
      duration: "1 week after 100%",
      description: "Stop writes to old cache. Remove old infrastructure.",
    },
  ],
  rollbackPlan:
    "Feature flag instantly routes all reads back to old cache. " +
    "Old cache remains warm during entire rollout period.",
  successCriteria: [
    { name: "cache_hit_rate", target: "> 90%", current: "67%" },
    { name: "p99_latency_ms", target: "< 50", current: "380" },
    { name: "error_rate", target: "< 0.01%", current: "0.3%" },
  ],
  monitoringAdditions: [
    "Dashboard comparing old vs new cache metrics",
    "Alert on hit rate drop below 85%",
    "Alert on P99 exceeding 100ms",
  ],
};

RFC Review Process

Set a decision deadline and define who approves. Open-ended review cycles that drag on for weeks defeat the purpose. Explicitly state what kind of feedback you want.

tstypescript
interface ReviewProcess {
  feedbackPeriod: string;
  decisionDeadline: string;
  requiredApprovers: string[];
  feedbackGuidance: string[];
  decisionCriteria: string;
}
 
const reviewProcess: ReviewProcess = {
  feedbackPeriod: "5 business days",
  decisionDeadline: "2025-10-01",
  requiredApprovers: [
    "Platform team lead",
    "Backend team lead",
    "SRE on-call",
  ],
  feedbackGuidance: [
    "Are there failure modes not covered in Risks?",
    "Are the alternatives fairly evaluated?",
    "Is the rollout plan sufficient for the risk level?",
    "Does the timeline account for your team's dependencies?",
  ],
  decisionCriteria:
    "Accepted if all required approvers agree. " +
    "If no consensus by deadline, escalate to engineering director.",
};

Key Takeaways

RFCs prevent expensive architecture mistakes by forcing structured thinking before code is written. Lead the motivation section with data—metrics, timelines, and impact—not opinions. Evaluate alternatives honestly; dismissing them superficially undermines credibility.

Include a detailed rollout plan with rollback strategy and success criteria. Set explicit review timelines and approver lists to prevent decision paralysis. The RFC itself becomes a decision record: months or years later, anyone can read it to understand not just what was built, but why this approach was chosen over the alternatives. Write the RFC you wish existed the last time you inherited a system with no documentation.

Wilfredo Rujel

Wilfredo Rujel

Full Stack Software Engineer

Share this postX