Build a Startup Cheatsheet
Trade-offs and When to Use What
Use this Build a Startup reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
The Core Tension in Technical Decisions
Every technical decision involves at least one of these tradeoffs:
- Speed to build vs. long-term flexibility
- Developer experience vs. runtime performance
- Managed services vs. control and cost
- Simple now vs. scalable later
There is no universally right answer. The right answer depends on your stage (zero to one vs. scaling), your team (solo founder vs. 10 engineers), your users (B2B enterprise vs. consumer), and your budget.
This page maps the most common decisions to the context where each choice wins.
Managed Service vs. Self-Hosted
| Managed service | Self-hosted | |
|---|---|---|
| Setup time | Minutes to hours | Days to weeks |
| Maintenance | Provider handles it | You handle it |
| Cost at low scale | Often free tier | Infrastructure cost even at zero users |
| Cost at high scale | Can be expensive | Cheaper per unit |
| Control | Limited | Full |
| Vendor lock-in | High | None |
| Reliability | Provider's SLA | Your ops team |
When managed wins: MVP, solo founder, team without dedicated infrastructure engineering, early-stage product where velocity matters most.
When self-hosted wins: At scale where managed costs exceed infrastructure costs; compliance requirements (HIPAA, SOC 2 with specific controls); need for custom configuration that managed services do not offer.
The typical crossover point: managed services become cost-prohibitive somewhere between $10K–$100K MRR, depending on your usage patterns. Self-host when you have the engineering bandwidth and a real cost problem — not before.
SQL vs. NoSQL
| SQL (Postgres) | NoSQL (MongoDB, DynamoDB) | |
|---|---|---|
| Data shape | Structured, relational | Flexible, document |
| Schema | Enforced, migrations required | Flexible, schema-on-read |
| Joins | Native, efficient | Manual, limited |
| Transactions | Full ACID | Partial or none |
| Scaling writes | Vertical + read replicas | Horizontal sharding |
| Querying | SQL (rich) | Limited (document-level) |
| Best for | 95% of products | Variable-shape documents, extreme write scale |
Choose SQL when: Your data has relationships (users → posts → comments), you need transactions (financial records), or you want to query your data flexibly.
Choose NoSQL when: Your document shapes vary wildly between records, you need horizontal write scaling across many shards at very high write volumes, or your team's expertise is in MongoDB.
Default: Postgres. The burden of proof is on NoSQL.
Server-Side Rendering vs. Client-Side Rendering
| SSR (Next.js App Router default) | CSR (pure React SPA) | |
|---|---|---|
| SEO | Excellent — content in initial HTML | Poor — crawlers may not execute JS |
| First paint | Faster (HTML from server) | Slower (JS must load first) |
| Interactivity time | Slightly slower (hydration) | Fast once loaded |
| Complexity | Higher (server/client boundary) | Lower |
| Caching | Per-page control | CDN edge caching of JS bundle |
| Best for | Marketing sites, content, dashboards | Internal tools, highly interactive apps |
Choose SSR: Public-facing pages where SEO matters, content-heavy pages, dashboards that need fresh data on load.
Choose CSR: Internal admin tools, apps behind login where SEO is irrelevant, highly interactive UIs (rich drag-and-drop, canvas-based tools).
The realistic choice: Next.js App Router with Server Components for data-fetching routes and "use client" only where interactivity is needed. This gives you the benefits of both.
Monolith vs. Microservices
| Monolith | Microservices | |
|---|---|---|
| Deployment | One unit | Many independent services |
| Development speed | Fast (one repo, shared types) | Slow (API contracts, separate deploys) |
| Debugging | Simple (one log stream) | Complex (distributed tracing) |
| Scaling | Scale everything or nothing | Scale individual services |
| Team coordination | Easy | Requires API versioning and contracts |
| Best for | 0–100 engineers | 100+ engineers, very high scale |
Default: monolith. Extract a service only when you have a specific, measured reason: dramatically different scaling needs (a video transcoder vs. a user API), a separate team that needs independent deploy, or a language boundary (Python ML inference alongside a Node.js web server).
Netflix, Amazon, and Uber all started as monoliths and split over years. Your startup is not Netflix.
Third-Party Auth vs. Hand-Rolled Auth
| Clerk / Supabase Auth / Auth.js | Custom auth | |
|---|---|---|
| Time to implement | 30 min – 2 hours | Days to weeks |
| Security | Battle-tested | Risk of implementation errors |
| Features | OAuth, MFA, passwordless included | Build each feature yourself |
| Cost | Free tier; $20–$25/month | Infrastructure only |
| Vendor lock-in | Medium to high | None |
| Control | Limited (Clerk) to full (hand-rolled) | Full |
| Best for | 99% of products | Auth IS your product |
Default: use a service. The only strong argument for hand-rolling auth is if your auth model is genuinely novel (which is very rare) or if you have strict data residency requirements that services cannot meet.
Stripe vs. Custom Payments
Do not build custom payment processing. Ever. The regulatory, security, and operational overhead (PCI DSS compliance, fraud handling, chargeback management) is not worth it. Stripe's fees (2.9% + 30¢) are the cost of delegating all of that to experts.
The only edge case: very high volume where Stripe's fees become prohibitive. At that point, you negotiate a custom rate with Stripe (available at ~$1M/month processing volume) before considering alternatives.
When to Use AI vs. Deterministic Logic
| Deterministic code | AI (LLM) | |
|---|---|---|
| Consistency | Always same output for same input | Variable output |
| Cost | Negligible compute | ~$0.001–$0.05 per call |
| Latency | Milliseconds | 0.5–10 seconds |
| Explainability | Fully auditable | Black box |
| Input flexibility | Structured, specific | Freeform, natural language |
| Best for | Calculations, validation, rules | Classification, summarization, generation |
Use AI when: The task involves understanding natural language, generating text, classifying open-ended input, or extracting structure from unstructured data.
Use deterministic logic when: The rules are clear and enumerable, consistency is required (financial calculations), you need sub-100ms response time, or cost per request must be near zero.
Common mistake: Using an LLM to do things that a regex or database query would do better and cheaper.
Serverless vs. Always-On Servers
| Serverless (Vercel, Cloudflare) | Always-on (Railway, EC2) | |
|---|---|---|
| Cold starts | 50ms–1s on first request | None |
| Scaling | Automatic, to zero | Manual or auto-scaling |
| Cost at zero load | $0 | ~$5–$20/month |
| Cost at high load | Per-invocation (can be expensive) | Flat or predictable |
| Long-running jobs | Not supported (15s–5min limit) | Supported |
| WebSockets | Not supported on most serverless | Supported |
| Stateful processes | Not possible | Possible |
Use serverless when: Standard CRUD web API, no background jobs, no WebSockets, traffic is bursty.
Use always-on when: WebSockets, background workers, cron jobs, long-running processes, ML inference, or cost-effective at high sustained traffic.
GraphQL vs. REST
| REST | GraphQL | |
|---|---|---|
| Simplicity | Simple, well-understood | More complex (schema, resolvers) |
| Data fetching | Multiple round trips for related data | Single query for any data shape |
| Overfetching | Common (return the whole resource) | Eliminated |
| Client flexibility | Fixed endpoints | Clients request exactly what they need |
| Caching | HTTP cache (GET requests) | Harder (POST by default) |
| Tooling | Ubiquitous | GraphQL-specific clients needed |
| Best for | Most products | Complex data graphs, multiple clients |
Default: REST. GraphQL adds significant complexity (schema, resolvers, n+1 query problems, authorization at field level). Its benefits matter when: you have multiple frontends (web + mobile + third-party) with different data needs, or your data is a complex graph that does not map cleanly to REST resources.
Choosing a Database ORM
| Raw SQL | Query builder (Knex) | ORM (Prisma, Drizzle) | |
|---|---|---|---|
| Type safety | None | Partial | Full (Drizzle, Prisma) |
| Control | Full | High | Medium |
| Migration management | Manual | Manual | Automatic |
| Learning curve | SQL knowledge required | Low | Low |
| Performance | Best | Very good | Good |
| Best for | Complex queries, DBA teams | Simple projects | Most TypeScript projects |
Recommendation: Drizzle ORM for TypeScript. It generates minimal abstraction overhead, gives you SQL-like syntax, and is fully type-safe.
Framework-Level Decisions: When to Break from the Default
The default stack (Next.js + Supabase + Vercel) covers most startup use cases. Deviate when:
| Situation | Alternative to consider |
|---|---|
| Mobile is your primary interface | Expo (React Native) |
| AI/ML is the core product | FastAPI (Python) for inference |
| Real-time collaboration is the product | Convex or Liveblocks |
| Heavy data pipeline or analytics | Python + Airflow/dbt |
| High-traffic public API | Go or Rust for latency-critical endpoints |
| Self-hosted auth requirement | Auth.js or hand-rolled |
| EU data residency | Hetzner Cloud (Germany), Scaleway (France), or Supabase EU region |
Never choose a non-default option because it is interesting or because you read a blog post about it. Measure the problem first.