Writing a High-Quality PR
First Impressions Matter
Maintainers decide in the first 30 seconds whether your PR is worth their time. Make those seconds count.
The 3-Second Test
When a maintainer opens your PR, they see:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WHAT MAINTAINERS SEE FIRST β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. PR Title β 3 seconds: Is this worth reading? β
β 2. First 3 lines of description β 10 seconds: Is this complete? β
β 3. Files changed count β 2 seconds: Is this focused? β
β 4. CI status β 1 second: Basic quality check β
β β
β If ANY of these fail β PR closed or ignored β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββPR Title Standards
The Format
type(scope): brief description
Examples:
β
fix(auth): prevent token expiration race condition
β
feat(api): add pagination to user list endpoint
β
docs(readme): fix installation instructions
β
test(auth): add unit tests for login flowTypes
| Type | When to Use | Example |
|---|---|---|
feat | New feature | feat(cart): add discount code support |
fix | Bug fix | fix(login): resolve password validation |
docs | Documentation | docs(api): update authentication guide |
test | Tests only | test(utils): add edge case coverage |
refactor | Code improvement | refactor(db): optimize query performance |
style | Formatting | style(css): fix indentation in navbar |
chore | Maintenance | chore(deps): update react to 18.2 |
perf | Performance | perf(api): reduce response time by 40% |
Title Quality Comparison
PR Description Structure
The Template
## Problem
Brief explanation of what issue this PR solves.
Fixes #123
## Solution
How this PR addresses the problem. Key technical decisions.
## Changes
- Changed X to handle Y
- Added Z for better performance
- Removed deprecated W
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manually tested scenarios A, B, C
## Screenshots (if UI changes)
Before: [image]
After: [image]
## Checklist
- [ ] Code follows project style
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] No breaking changesReal Example: Good PR
## Problem
Users lose form data when session expires (#456)
## Solution
Implemented client-side form state persistence using localStorage.
Falls back gracefully if localStorage is unavailable.
## Changes
- Added `FormPersistence` utility class
- Hooked into form `onChange` events
- Auto-restore on mount if data exists
- Clear storage on successful submit
## Testing
- [x] Unit tests for FormPersistence class
- [x] Integration test for auto-restore
- [x] Manual testing:
- Fill form β refresh β data restored β
- Submit form β data cleared β
- Private browsing β graceful fallback β
## Breaking Changes
None
## Additional Notes
Considered IndexedDB but localStorage sufficient for form sizes < 5MBCode Quality Checklist
Before Submitting
- Code runs locally without errors
- All tests pass
- No console.log() or debug statements
- No commented-out code
- Code follows project style guide
- No unnecessary dependencies added
- Error handling implemented
- Edge cases considered
Self-Review
Read Your Own Code
Go through every changed file. Would YOU approve this?
Check the Diff
No accidental whitespace changes, formatting differences, or unrelated files.
Run Linters
Fix all linting errors before submitting.
Test Edge Cases
What happens with empty input? Null values? Huge datasets?
PR Size Guidelines
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PR SIZE CHART β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Lines Changed Merge Probability Review Time β
β ββββββββββββββ βββββββββββββββββ βββββββββββ β
β β
β 1-50 95% < 30 mins β
β 51-200 80% 1-2 hours β
β 201-500 50% 4-8 hours β
β 501-1000 25% 1-2 days β
β 1000+ 10% Weeks (if ever) β
β β
β Sweet spot: 50-200 lines β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββBreak Large Changes into Smaller PRs
Instead of one 800-line PR, submit four 200-line PRs that build on each other.
Common PR Mistakes
π« Mistake 1: Too Many Changes
β BAD PR:
Files changed: 47
- Added feature X
- Fixed bugs Y, Z
- Refactored module A
- Updated documentation
- Changed config filesβ
GOOD PR:
Files changed: 5
- Added feature X with tests
- Updated relevant documentationπ« Mistake 2: No Context
β BAD:
## Description
Fixed the bug
## Changes
Changed some filesβ
GOOD:
## Problem
Users couldn't log in with emails containing + character (#789)
## Solution
Updated email validation regex to include RFC 5322 compliant characters
## Changes
- Modified `validateEmail()` in auth/validation.js
- Added test cases for edge case emails
- Updated email regex pattern
## Testing
- [x] Test suite passes
- [x] Tested with email: user+tag@example.com β
π« Mistake 3: Failing CI
Never Submit with Broken CI
If your PR has failing tests or linting errors on GitHub, maintainers won't even look at it. Fix CI first.
π« Mistake 4: Merge Conflicts
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PR with Conflicts β
β ββββββββββββββββββ β
β β
β [!] This branch has conflicts that must be resolved β
β β
β Maintainer's reaction: "Come back when it's ready" β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββDraft vs Ready PRs
When to Use Draft
- Still working on the implementation
- Want early feedback on approach
- CI not passing yet
- Not ready for full review
Draft PR Title
[WIP] feat(api): add GraphQL support
## Status
π§ Work in Progress
Seeking feedback on:
- [ ] GraphQL schema design
- [ ] Authentication approach
TODO:
- [ ] Add remaining mutations
- [ ] Write tests
- [ ] Update docsLinking Issues
Always link to the issue your PR addresses:
β
Fixes #123
β
Closes #123
β
Resolves #123
β οΈ Related to #123
β οΈ Part of #123
β Fixes issue with login
β Addresses the bugGitHub keywords that auto-close issues:
fix,fixes,fixedclose,closes,closedresolve,resolves,resolved
Screenshots for UI Changes
Always Include
Before State
Show what it looked like before your changes.
After State
Show your implementation.
Different States
Loading, error, empty, populated states.
Responsive Views
Desktop, tablet, mobile if applicable.
Screenshot Template
## Screenshots
### Before

### After

### Mobile View

### Edge Cases
- Empty state: 
- Error state: 
- Loading state: The Perfect PR Checklist
- Clear, descriptive title following conventions
- Comprehensive description with problem/solution
- Links to related issue(s)
- Code is self-reviewed
- Tests added/updated and passing
- Documentation updated if needed
- No merge conflicts
- CI passing (all green)
- Focused scope (less than 200 lines if possible)
- Screenshots for UI changes
- Breaking changes documented
- No debugging code or comments
After Submitting
Don't Just Walk Away
- Monitor for feedback - Check daily
- Respond within 24 hours - Show you're engaged
- Keep it updated - Rebase if main moves forward
- Be patient - Reviews take time
If No Response After 1 Week
Polite ping:
"Hi @maintainer,
Just checking if you've had a chance to review this PR.
Happy to make any changes needed. No rush!
Thanks!"Patience Wins
Good PRs sometimes sit for weeks. Don't take it personallyβmaintainers are busy.
Example: Stellar PR
feat(api): add rate limiting to authentication endpoints
## Problem
Authentication endpoints are vulnerable to brute force attacks (#891)
## Solution
Implemented sliding window rate limiting using Redis:
- 5 attempts per IP per minute
- 20 attempts per IP per hour
- Configurable via environment variables
## Changes
- Added `RateLimiter` middleware
- Updated auth routes to use rate limiter
- Added Redis dependency for distributed rate limiting
- Created unit and integration tests
## Technical Decisions
- Chose sliding window over fixed window for fairness
- Used Redis for distributed systems (multiple server instances)
- Made limits configurable for different deployment environments
## Testing
- [x] Unit tests for RateLimiter class (100% coverage)
- [x] Integration tests for auth endpoints
- [x] Load tested with 1000 concurrent requests
- [x] Verified rate limit headers in response
Manual testing:
- Exceeded rate limit β 429 response β
- Rate limit resets after window β 200 response β
- Multiple IPs β independent limits β
## Breaking Changes
None. Rate limiting is opt-in via `ENABLE_RATE_LIMIT` env var.
## Performance Impact
- < 1ms latency per request
- Redis memory: ~1KB per IP address
- Tested with 10,000 unique IPs
## Documentation
- Updated API docs with rate limit headers
- Added rate limiting section to README
- Included configuration examples
## Screenshots
N/A (API change only)
## Checklist
- [x] Code follows project style guide
- [x] Tests pass locally
- [x] Documentation updated
- [x] No breaking changes
- [x] Security implications considered