Clean Code for TypeScript/JavaScript
Practical patterns for writing and refactoring clean, maintainable TypeScript/JavaScript code.
Code Smell Detection
Naming Smells
Smell Example Fix
Single-letter names const d = new Date()
const createdAt = new Date()
Abbreviations const usrMgr = ...
const userManager = ...
Generic names data , info , temp , result
Name by what it represents
Misleading names userList (but it's a Set) users or userSet
Encoding types strName , arrItems
name , items
Function Smells
Smell Indicator Refactoring
Too many parameters
3 params Extract to options object
Flag arguments process(data, true)
Split into separate functions
Side effects hidden Function does more than name suggests Rename or split
God function
20 lines or multiple responsibilities Extract smaller functions
Deep nesting
3 levels of indentation Early returns, extract functions
Structure Smells
Smell Indicator Refactoring
Long file
300 lines Split by responsibility
Feature envy Function uses another object's data extensively Move to that object
Data clumps Same group of params passed together Extract to type/class
Primitive obsession Using primitives for domain concepts Create value objects
Switch on type if (type === 'A') ... else if (type === 'B')
Polymorphism or lookup
Refactoring Patterns
For detailed before/after code examples, see references/refactoring-patterns.md.
Quick reference:
-
Guard clauses — Replace nested if/else with early returns
-
Object lookup — Replace switch/if-else chains with Record mapping
-
Options object — Replace >3 parameters with a single options object
-
Named constants — Replace magic numbers/strings with descriptive constants
-
Extract function — Break god functions into single-responsibility pieces
-
Flatten ternaries — Replace nested ternaries with lookup or function
-
Consolidate duplicates — Extract repeated logic into shared functions
-
Simplify booleans — Return condition directly instead of if/else true/false
-
Array methods — Replace manual loops with filter/map/reduce
-
Destructure — Use destructuring for cleaner property access
Naming Conventions
Functions
-
Actions: verb + noun → createUser , validateInput , fetchOrders
-
Booleans: is/has/can/should prefix → isValid , hasPermission , canEdit
-
Event handlers: handle + event → handleClick , handleSubmit
-
Transformers: to/from/parse/format → toJSON , fromDTO , parseDate
Variables
-
Booleans: positive names → isEnabled not isNotDisabled
-
Arrays/collections: plural → users , orderItems
-
Counts: noun + Count → retryCount , errorCount
Constants
-
Module-level: SCREAMING_SNAKE_CASE → MAX_RETRIES , API_BASE_URL
-
Enum-like objects: PascalCase key, values match usage → Status.Active
Quick Refactoring Checklist
When reviewing code for cleanliness:
-
Names — Can you understand intent without reading implementation?
-
Functions — Does each do exactly one thing? <20 lines?
-
Parameters — More than 3? Extract to object
-
Nesting — More than 2 levels? Use early returns
-
Duplication — Same logic in 2+ places? Extract
-
Magic values — Any unexplained numbers/strings? Name them
-
Comments — Explaining "what"? Rewrite code to be self-explanatory
-
Dead code — Commented-out code or unused vars? Delete
Anti-Patterns to Avoid
Over-Engineering
-
Don't create abstractions for single-use cases
-
Don't add configurability "for the future"
-
Don't wrap simple operations in utility functions
Under-Engineering
-
Don't ignore repeated patterns (3+ occurrences = extract)
-
Don't leave deeply nested code unrefactored
-
Don't use any to avoid typing properly
Wrong Abstraction
-
Don't force inheritance when composition works
-
Don't create god classes that do everything
-
Don't split tightly coupled logic across files