Claude-skill-registry js-ts-performance-readability

Write JavaScript/TypeScript that is performant, readable, and easy to trust with minimal inspection. Use when generating or reviewing JS/TS code—scripts, utilities, data processing, or performance-sensitive logic—so outputs are correct, fast, and maintainable without heavy manual review.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/js-ts-performance-readability" ~/.claude/skills/majiayu000-claude-skill-registry-js-ts-performance-readability && rm -rf "$T"
manifest: skills/data/js-ts-performance-readability/SKILL.md
source content

JavaScript/TypeScript performance, readability, and trust

Apply these practices so generated code is correct, performant, and readable with minimal inspection.

Goals

  • Correctness: Explicit types, no silent coercion, clear edge handling.
  • Performance: Prefer Map/Set for lookups, avoid redundant work, use appropriate data structures and async patterns.
  • Readability: Small named helpers, modular structure, predictable control flow.
  • Trust: Code that type-checks and follows consistent patterns is easier to verify at a glance.

1. Model the problem first

Before writing code, understand what you're solving and what can go wrong.

Clarify inputs and outputs

  • What is the input? File, API response, user input, command-line args? What's the shape and size?
  • What is the expected output? A transformed value, a file, a side effect? What's the contract?
  • What are the constraints? Size limits, time constraints, memory concerns?

Identify edge cases upfront

Think through edge cases before implementation—they're easier to handle when designed for, not patched in:

  • Empty input: Empty string, empty array, null/undefined. What should happen?
  • Single element: Does your logic assume at least 2 items? Handle the 1-item case.
  • Boundary values: Zero, negative numbers, MAX_SAFE_INTEGER, very long strings.
  • Invalid/malformed input: Missing fields, wrong types, unexpected formats.
  • Duplicates: Does your logic handle or assume uniqueness?
  • Order sensitivity: Does input order matter? What about sorted vs unsorted?

Design tests before or alongside code

  • Write test cases (or at least list them) for: happy path, edge cases, error cases.
  • For scripts, define example inputs and expected outputs before implementing.
  • If the problem is complex, start with the simplest case that exercises the core logic, get that working, then expand.

When in doubt, ask

If requirements are ambiguous, clarify before implementing. Questions like:

  • "What should happen if X is empty?"
  • "Should this throw or return a default for invalid input?"
  • "Is this expected to handle very large inputs?"

Getting alignment on edge cases early prevents rework.


2. Correctness and trust

Use explicit types

  • Prefer TypeScript with explicit types for inputs and outputs (e.g.
    type Config = { name: string; timeout: number; retries: number }
    ).
  • Use
    as const
    for tuple return types so callers get narrow types:
    return [success, count] as const
    .
  • Define return types explicitly for public functions; let inference handle internal helpers.

Avoid silent coercion

  • Parse numbers explicitly:
    line.split(" ").map(Number)
    or
    Number(ch)
    ; validate with
    !isNaN(n)
    or
    Number.isNaN(n)
    when needed.
  • Use nullish coalescing for "default if missing":
    map.get(key) ?? 0
    ,
    arr.find(predicate) ?? fallback
    .

Handle edges explicitly

  • Check "no value" with
    next == null
    (or
    next === undefined
    ) and return early.
  • When mutating shared state for a branch (e.g. backtracking search), restore state after the branch so other branches see clean state.

3. Performance

Prefer Map and Set for lookups and deduplication

  • Use
    Set
    for "visited" and "already seen" (e.g. BFS/DFS, loop detection).
  • Use
    Map
    for frequency counts or keyed caches:
    freq.set(key, (freq.get(key) ?? 0) + 1)
    .

Avoid redundant work

  • Parse input once; reuse the parsed structure across multiple operations or stages.
  • For recursive or repeated computations, memoize with a deterministic cache key (e.g. serialize relevant state to a string). Prefer a single cache Map keyed by string.

Use appropriate algorithms and data structures

  • BFS/DFS: explicit queue/stack and
    Set
    for visited.
  • When the problem involves distances or costs, consider precomputing distances (e.g. BFS from key nodes) then doing targeted search instead of raw recursion over the full graph.
  • Prefer
    .reduce((acc, n) => acc + n, 0)
    or a small
    sum(arr)
    helper for numeric aggregation instead of manual loops when it stays readable.

Prefer single-pass or linear structures where possible

  • Use
    .find(predicate)
    and
    .findLast(predicate)
    instead of scanning manually twice.
  • Use
    toSorted((a, b) => a - b)
    (or
    .slice().sort(...)
    ) when you need a sorted copy without mutating the original.
  • Avoid chaining methods that each iterate the full array when a single pass would suffice (e.g.
    .filter().map()
    when
    .flatMap()
    or
    .reduce()
    could do both).

Async patterns: parallelize independent work

  • Promise.all
    : Use when you have independent async operations that can run concurrently. Fails fast—if any promise rejects, the whole result rejects.
    const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);
    
  • Promise.allSettled
    : Use when you need results from all promises regardless of individual failures. Returns
    { status: 'fulfilled', value }
    or
    { status: 'rejected', reason }
    for each.
    const results = await Promise.allSettled(urls.map(fetch));
    const succeeded = results.filter(r => r.status === 'fulfilled').map(r => r.value);
    
  • Promise.race
    : Use when you want the first promise to settle (e.g. timeout patterns).
  • Avoid sequential awaits for independent work:
    // Bad: sequential, takes sum of times
    const a = await fetchA();
    const b = await fetchB();
    
    // Good: parallel, takes max of times
    const [a, b] = await Promise.all([fetchA(), fetchB()]);
    

Avoid common performance traps

  • Regex in loops: Compile regex once outside the loop (
    const re = /pattern/g
    ), not inside.
  • Repeated JSON.parse/stringify: Parse once, pass the object; don't serialize/deserialize repeatedly.
  • Unnecessary cloning: Use spread or
    structuredClone
    only when mutation is a real concern; don't defensively clone everything.
  • String concatenation in tight loops: Use array +
    .join()
    or template literals for building large strings.

4. Readability and structure

Small named helpers

  • Extract pure helpers:
    sum
    ,
    groupBy
    ,
    keyBy
    ,
    chunk
    ,
    unique
    ,
    clamp
    ,
    debounce
    .
  • Keep helpers focused; name them by intent (e.g.
    parseConfig
    ,
    validateInput
    ,
    formatOutput
    ) so the main flow reads like prose.

Modular structure

  • Separate concerns: parsing/input, processing/logic, output/formatting.
  • Use a clear entry point (e.g.
    main()
    ,
    run()
    ,
    process()
    ) that orchestrates the stages; keep it thin so the flow is obvious.
  • Group related logic into functions or modules; avoid monolithic functions that do parsing, processing, and output all in one.

Prefer const and explicit control flow

  • Use
    const
    by default; use
    let
    only when reassignment is needed.
  • Use early returns for edge cases; avoid deep nesting.
  • In loops, use
    continue
    for "skip this item" and keep the main path obvious.

Parsing and input handling

  • Prefer declarative parsing:
    input.split("\n").map(line => line.split(" ").map(Number))
    when the format is simple.
  • For complex parsing, use a small function or iterator so the main logic stays clear.
  • Validate input shape early; fail fast with clear error messages rather than silently producing garbage.

5. Consistency and style

  • One way to do things: Use either
    .reduce()
    or a
    for
    loop for summing, but don't mix styles randomly in the same file.
  • Naming: Use consistent names across the codebase (e.g. always
    visited
    or always
    seen
    , not both). Name variables by what they represent, not by type (
    users
    not
    userArray
    ).
  • Error handling: Be consistent—either throw exceptions, return
    Result
    types, or return
    null
    /
    undefined
    , but pick one pattern per codebase or module.

6. Checklist before suggesting code

  • Problem modeled: Inputs, outputs, and edge cases identified before implementation.
  • Edge cases covered: Empty, single-element, boundary values, invalid input handled explicitly.
  • Tests considered: Happy path + edge cases have test cases (or at least documented expectations).
  • Types are explicit for any non-trivial data (inputs, outputs, intermediate structures).
  • No silent coercion; numbers parsed explicitly and validated when needed.
  • Map/Set used for lookups and deduplication; no unnecessary array scans.
  • Independent async operations use
    Promise.all
    or
    Promise.allSettled
    , not sequential awaits.
  • If mutating shared state in a branch, state is restored after the branch.
  • Helpers are small and named by intent; main flow is easy to follow.
  • Common perf traps avoided (regex in loops, repeated parsing, unnecessary cloning).

Following these practices keeps JS/TS code correct, fast, and easy to trust with minimal inspection.