JavaScript is messy. Still, it has a few patterns that feel so sharp and expressive that losing them would hurt.
function createCounter() {
let count = 0
return {
inc: () => ++count,
dec: () => --count,
value: () => count
}
}A function can carry its own tiny universe around with it.
Why this is unfair:
- private state, no class required
- tiny API surface
- perfect for factories, memoization, once-handlers, and event logic
const parser = createParser({
number: (value) => Number(value),
boolean: (value) => value === "true",
fallback: () => null
})In JavaScript, behavior is just data with better posture.
Why this is unfair:
- no strategy pattern ceremony
- callers can inject logic directly
- APIs become small, flexible, and composable
function createUser({ id, name, role = "user", active = true }) {
return { id, name, role, active }
}The parameter list tells you the shape, the defaults, and the intent in one shot.
Why this is unfair:
- self-documenting function signatures
- defaults live exactly where you need them
- config-heavy code stays surprisingly clean
const user = await fetchUser(id)
const team = await fetchTeam(user.teamId)
const report = await buildReport(team)When JavaScript is doing I/O-heavy work, async/await makes orchestration feel almost suspiciously readable.
Why this is unfair:
- async code keeps linear shape
- easy to compose network, file, and database work
- "what happens next" stays obvious
const emails = users
.filter((user) => user.active)
.filter((user) => user.role === "admin")
.map((user) => user.email)A good JS data pipeline reads like you already solved the problem.
Why this is unfair:
- tiny building blocks
- no loop bookkeeping
- easy to read, easy to change, easy to trust
JavaScript is not elegant by default.
But closures, function-passing, destructuring, async flow, and tiny composable pipelines give it a kind of expressive power that feels hard to replace once you're used to it.