javascript, #architecture, #webdev, #typescript
Most teams don’t pick bad tools. They waste time picking them. These patterns turn vague “it depends” debates into fast, defensible decisions.
1. Replace Endless npm Debates With a 5-Minute Score Script
Instead of arguing about packages, score them with objective signals.
Before
// "Looks popular, let's try it"
npm install some-library
After
type PackageScore = {
downloads: number
lastCommitDays: number
openIssues: number
deps: number
bundleSizeKb: number
}
function score(pkg: PackageScore) {
let score = 0
if (pkg.downloads > 1_000_000) score += 2
if (pkg.lastCommitDays < 30) score += 2
if (pkg.openIssues < 50) score += 1
if (pkg.deps < 5) score += 1
if (pkg.bundleSizeKb < 10) score += 1
return score
}
You stop guessing and start comparing. This cuts package decisions from hours to minutes and eliminates 80% of bad installs.
2. Turn “Build vs Install” Into a Replaceability Function
The real question is not complexity. It’s replacement cost.
Before
// install a lib for simple formatting
import format from "date-lib"
After
function formatRelative(date: Date) {
const diff = Date.now() - date.getTime()
const minutes = Math.floor(diff / 60000)
if (minutes < 1) return "just now"
if (minutes < 60) return `${minutes}m ago`
return date.toLocaleDateString()
}
function shouldInstall(replacementHours: number) {
return replacementHours > 8
}
If you can rewrite it in under a day, don’t install it. This removes unnecessary dependencies and reduces attack surface.
3. Encode Team Constraints Instead of Comparing Features
Framework debates fail because people compare features instead of constraints.
Before
// React vs Vue vs Svelte discussion
// "which is better?"
After
type Constraints = {
teamExperience: "react" | "vue" | "none"
deadlineWeeks: number
performanceCritical: boolean
}
function pickFramework(c: Constraints) {
if (c.teamExperience === "react") return "react"
if (c.performanceCritical) return "svelte"
return "vue"
}
This removes 90% of options instantly. Features don’t decide. Constraints do.
If you go deeper into tradeoffs, this aligns with real-world comparisons like Vite vs Next.js vs Remix framework comparison for 2025, where ecosystem constraints dominate feature differences.
4. Replace Opinion Battles With a Weighted Decision Matrix
Instead of arguing, score decisions based on what actually matters.
Before
// "Redux is better"
// "No Zustand is simpler"
After
type Option = {
name: string
familiarity: number
bundle: number
ecosystem: number
}
const weights = {
familiarity: 0.4,
bundle: 0.3,
ecosystem: 0.3
}
function evaluate(option: Option) {
return (
option.familiarity * weights.familiarity +
option.bundle * weights.bundle +
option.ecosystem * weights.ecosystem
)
}
const zustand = evaluate({
name: "zustand",
familiarity: 7,
bundle: 9,
ecosystem: 7
})
const redux = evaluate({
name: "redux",
familiarity: 10,
bundle: 6,
ecosystem: 10
})
You get a number, not an opinion. Decisions become reproducible and explainable.
5. Convert Migration Fear Into a Cost Function
Most teams underestimate migration cost. Make it explicit.
Before
// "Fastify is faster, let's migrate"
After
function migrationCost({
files,
hoursPerFile,
testMultiplier = 2
}: {
files: number
hoursPerFile: number
testMultiplier?: number
}) {
return files * hoursPerFile * testMultiplier
}
function migrationWorth(cost: number, benefitMs: number) {
return benefitMs > cost
}
const cost = migrationCost({
files: 150,
hoursPerFile: 1.5
}) // ~450 hours
const benefit = 120 // ms improvement
const shouldMigrate = migrationWorth(cost, benefit)
Most migrations fail this test. This pattern prevents months of wasted effort.
6. Replace Big-Bang Rewrites With the Strangler Pattern
Never rewrite everything at once. Route new code through the new system.
Before
// rewrite entire API
app.use("/api", newApi)
After
app.use("/api/v1", oldApi)
app.use("/api/v2", newApi)
app.get("/api/users", (req, res) => {
return useNewVersion(req)
? newApi(req, res)
: oldApi(req, res)
})
You migrate gradually, not catastrophically. Risk drops to near zero.
7. Capture Decisions as Code Instead of Forgetting Them
Most teams forget why they chose something. Encode it.
Before
// nobody remembers why Zustand was chosen
After
export const ADR_007 = {
decision: "zustand",
context: {
widgets: 12,
team: 3,
experience: "react-heavy"
},
alternatives: ["redux", "context"],
reason: "less boilerplate, faster iteration",
tradeoffs: ["weaker devtools"]
}
This prevents future re-debates and saves hours in onboarding and refactoring.
You don’t need better opinions. You need better decision primitives.
Take one of these patterns and apply it to your next choice. Start with the scoring function or the migration cost. Within one sprint, your team will stop debating and start shipping.
Top comments (0)