Write idiomatic TypeScript and JavaScript with efficiency patterns
Idiomatic-efficiency guidelines for TypeScript, JavaScript, and React across arrays, async code, types, and closures.
Maintainer of this project? Claim this page to edit the listing.
13.5.0Add to Favorites
Why it matters
Developers hire this skill to transform verbose, imperative TypeScript and JavaScript code into concise, idiomatic patterns that leverage modern language features like destructuring, async/await, optional chaining, and functional array methods while avoiding common anti-patterns.
Outcomes
What it gets done
Replace imperative loops with declarative array methods (map, filter, reduce)
Refactor promise chains and sequential awaits into parallel async patterns
Convert manual object manipulation into spread operators and destructuring
Eliminate TypeScript type assertions and 'any' with proper type guards and generics
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-typescript | bash Overview
TypeScript / JavaScript: Idiomatic Efficiency Reference
Pairs verbose or non-idiomatic TypeScript, JavaScript, and React patterns against their preferred equivalents across arrays, destructuring, async code, functions, types, and components, plus a table of smaller TS/JS anti-patterns. Use it when writing, reviewing, or refactoring TypeScript, JavaScript, or React code toward idiomatic style. It covers language-level patterns only, not architectural decisions - apply judgment against readability.
What it does
Gives idiomatic-efficiency guidelines for TypeScript and JavaScript across seven areas: array and object operations, destructuring and spread, async and promises, functions and closures, TypeScript types, React, and TS/JS-specific anti-patterns. Each area pairs a verbose or non-idiomatic pattern directly against the preferred equivalent.
When to use - and when NOT to
Use this skill when a task matches language-specific super-code guidelines for TypeScript. It's scoped narrowly on purpose: these cover line-level pattern choices, like filter/map versus a loop or type versus interface, not overall architectural decisions. Applying every rule mechanically is itself flagged as a mistake in its own right - over-compression can reduce readability, so a reviewer's judgment about a specific case should always win over blindly collapsing a pattern just because a rule exists for it.
Inputs and outputs
Array and object operations favor a chained filter-and-map over an imperative push loop, reduce over a manual running-total loop, object spread over an Object.assign-then-override sequence, and optional chaining over an explicit existence check before property access:
const result = items.filter(i => i.active).map(i => i.name.toUpperCase())
const total = orders.reduce((sum, o) => sum + o.amount, 0)
const city = user.address?.city
Destructuring and spread favor object destructuring over separate variable assignments pulled off the same object, array destructuring over manual index access, array spread over a chain of concat calls, and rest-destructuring to omit a single key over a mutating delete on a shallow copy. Async code favors async/await wrapped in try/catch over a chained then-then-catch promise pipeline, Promise.all for genuinely independent operations run in parallel over sequential awaits that needlessly serialize unrelated work, and awaiting an already-async function directly instead of wrapping it in a redundant new Promise executor. Awaiting inside a map callback without Promise.all is called out specifically as a real mistake, since it silently sequences work that was supposed to run concurrently, quietly turning a parallel operation into a slow serial one.
Integrations
For functions and closures, an arrow-function expression body replaces an unnecessary block-and-return, a default parameter value replaces an if-guard reassignment inside the function body, and plain top-level module statements replace an immediately-invoked function expression used for no real isolation reason. In TypeScript's own type system, the compiler is trusted to infer an obvious, simple return type instead of writing it out explicitly; an unknown type paired with a type guard, or a proper generic constraint, does the job a bare any escape hatch would otherwise do unsafely; a single-use prop gets an inline object-shape type rather than a redundant standalone interface, with that interface only getting extracted once the shape is genuinely reused in two or more places; and a runtime instanceof check replaces a type assertion that silences a real type error and can crash unpredictably if the assertion turns out to be wrong. As a general rule of thumb, type suits unions, intersections, and simple aliases, while interface suits extensible object shapes meant to be built on later. React gets its own set of concerns: derived state gets computed directly during render instead of mirrored a render late through useEffect and an extra state variable; useCallback gets reserved for a handler actually passed into a memoized child or used as an effect dependency, not wrapped around every event handler out of habit; an object-literal prop gets memoized with useMemo or hoisted rather than reallocated fresh on every render; and a stable, meaningful item id serves as a list key in place of the raw array index whenever the list can reorder or be filtered. Ten smaller, more mechanical issues round out a dedicated anti-pattern table: loose equality against null, a manual typeof undefined string comparison, double-negation boolean coercion, var left in place of const/let, for...in iteration over an array, an uninterpolated template literal, a leftover console.log in production code, hand-iterating Object.keys instead of Object.entries, ternaries nested more than two levels, and a silently swallowing empty catch block.
Who it's for
Someone reviewing a pull request or cleaning up their own draft who needs the exact preferred replacement for a specific line, not a general style philosophy - the anti-pattern table alone gives ten concrete swaps that would otherwise each need their own explanation in a code-review comment.
Source README
TypeScript / JavaScript: Idiomatic Efficiency Reference
When to Use
- Use this skill when the task matches this description: Language-specific super-code guidelines for typescript.
Table of Contents
- Array & Object Operations
- Destructuring & Spread
- Async / Promises
- Functions & Closures
- TypeScript Types
- React (if applicable)
- Anti-patterns specific to TS/JS
1. Array & Object Operations {#arrays}
// ❌ Imperative push loop
const result: string[] = []
for (const item of items) {
if (item.active) result.push(item.name.toUpperCase())
}
// ✅
const result = items.filter(i => i.active).map(i => i.name.toUpperCase())
// ❌ Manual reduce for sum
let total = 0
for (const o of orders) total += o.amount
// ✅
const total = orders.reduce((sum, o) => sum + o.amount, 0)
// ❌ Manual object copy + override
const updated = Object.assign({}, user)
updated.name = "Alice"
// ✅
const updated = { ...user, name: "Alice" }
// ❌ Existence check before property access
const city = user.address ? user.address.city : undefined
// ✅
const city = user.address?.city
2. Destructuring & Spread {#destructuring}
// ❌ Separate variable assignments
const name = user.name
const age = user.age
// ✅
const { name, age } = user
// ❌ Index access for array elements
const first = arr[0]
const second = arr[1]
// ✅
const [first, second] = arr
// ❌ Merging arrays with concat
const merged = a.concat(b).concat(c)
// ✅
const merged = [...a, ...b, ...c]
// ❌ Omitting a key by delete (mutates)
const copy = { ...obj }
delete copy.password
// ✅ — destructure to omit
const { password, ...safe } = obj
3. Async / Promises {#async}
// ❌ Promise chain when async/await is cleaner
fetchUser(id)
.then(user => fetchOrders(user.id))
.then(orders => process(orders))
.catch(handleError)
// ✅
try {
const user = await fetchUser(id)
const orders = await fetchOrders(user.id)
process(orders)
} catch (e) {
handleError(e)
}
// ❌ Sequential awaits for independent operations
const user = await fetchUser(id)
const config = await fetchConfig()
// ✅ — run in parallel
const [user, config] = await Promise.all([fetchUser(id), fetchConfig()])
// ❌ Wrapping already-async function in new Promise
const result = await new Promise((resolve) => {
someAsyncFn().then(resolve)
})
// ✅
const result = await someAsyncFn()
Don't await inside a .map() without Promise.all - it sequences what should be parallel.
4. Functions & Closures {#functions}
// ❌ Arrow function with unnecessary block body
const double = (x: number) => { return x * 2 }
// ✅
const double = (x: number) => x * 2
// ❌ Default parameter with if-guard
function greet(name?: string) {
if (!name) name = "World"
return `Hello, ${name}`
}
// ✅
function greet(name = "World") {
return `Hello, ${name}`
}
// ❌ IIFE for no reason in module scope
;(function() {
const x = compute()
doSomething(x)
})()
// ✅ — just top-level statements in a module
const x = compute()
doSomething(x)
5. TypeScript Types {#types}
// ❌ Explicit return type when inference is obvious
function add(a: number, b: number): number {
return a + b
}
// ✅ — let TS infer simple return types
function add(a: number, b: number) {
return a + b
}
// ❌ any
function process(data: any) { ... }
// ✅ — use unknown + type guard, or a proper type/generic
function process<T extends Record<string, unknown>>(data: T) { ... }
// ❌ Redundant interface for single-use inline shape
interface UserNameProps { name: string }
function UserName({ name }: UserNameProps) { ... }
// ✅ — inline for single-use
function UserName({ name }: { name: string }) { ... }
// Extract interface when reused in 2+ places
// ❌ Type assertion (as) to silence a real type error
const el = document.getElementById("app") as HTMLDivElement
el.innerText = "hi" // crashes if el is null
// ✅
const el = document.getElementById("app")
if (!(el instanceof HTMLDivElement)) throw new Error("Missing #app")
el.innerText = "hi"
Prefer type for unions/intersections/aliases; interface for extensible object shapes.
6. React (if applicable) {#react}
// ❌ Effect for derived state
const [doubled, setDoubled] = useState(0)
useEffect(() => { setDoubled(count * 2) }, [count])
// ✅ — compute during render
const doubled = count * 2
// ❌ useCallback everywhere by default
const handler = useCallback(() => doSomething(id), [id])
// ✅ — only when passed to memoized child or used as effect dep
// Otherwise: const handler = () => doSomething(id)
// ❌ Passing object literal as prop (new reference each render)
<Component config={{ debug: true }} />
// ✅
const config = useMemo(() => ({ debug: true }), [])
<Component config={config} />
// Or if truly static: define outside component
const CONFIG = { debug: true }
// ❌ Index as key in list that can reorder/filter
items.map((item, i) => <Row key={i} {...item} />)
// ✅
items.map(item => <Row key={item.id} {...item} />)
7. Anti-patterns specific to TS/JS {#antipatterns}
| Anti-pattern | Preferred |
|---|---|
== null (loose) |
=== null or ?? / ?. |
typeof x === "undefined" |
x === undefined or x == null (when both null/undefined ok) |
!!x when boolean coercion is implied |
Boolean(x) for clarity, or just x in conditionals |
var |
const by default, let when reassigned |
for...in on arrays |
for...of or array methods |
| String template literal with no interpolation | plain string '...' |
console.log left in production code |
remove or use a logger |
Object.keys(obj).forEach(...) |
for (const [k, v] of Object.entries(obj)) |
| Nested ternaries beyond 2 levels | if/else or early return |
try { ... } catch (e) {} (silent swallow) |
log or rethrow |
Limitations
- These are language-specific guidelines and do not cover overall architectural decisions.
- Over-compression might reduce readability; apply judgement.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.