Coding Style Guide
Consistent
Code that is written consistently reduces cognitive load for developers. It eliminates disagreement over things that do not make a difference to delivering high quality user-focused software.
Consistent (often rigid) application of decided patterns and styles makes a codebase predictable. It makes it easier to navigate, empowers teammates to work in new areas and codebases, and leaves more mental space for considering product, UX/UI and other business problems.
Explicit
Code that is explicit leaves no room for misinterpretation. It explains and contextualizes itself by nature of its structure and naming. It enables teammates to read code quickly and with less external resources.
Explicitness often means sacrificing short or "neater" names for ones that state exactly what they are, sometimes with silly verbosity.
don’t:
const [idx, setIndex] = useState(0);do:
const [firstSelectedIndex, setFirstSelectedIndex] = useState(0);Explicit code strives for clarity, not just in naming. Overly terse or "clever" code can lack explicitness insofar as it may defy expectation or intuition. We want to spend time understanding complexity in business logic, not esoteric code styles. A classic example is the use of Array.reduce for transforming objects.
don’t:
const parsedData = Object.keys(data).reduce(
(next, key) => ({
...next,
[key]: parseFloat(data[key]),
}),
{}
);do:
const parsedData = {};
for (const key of Object.keys(data)) {
parsedData[key] = parseFloat(data[key]);
}Pragmatic
Pragmatism in code is about considering tradeoffs. It requires that we are grounded in the larger picture of our work, that we understand what and why we are writing code. To be Pragmatic in engineering is to build the appropriate thing, within the context of product requirements, user empathy, and technical constraints.
The goal of Pragmatic code is to balance the scales between delivering user value, quality software, while enabling high velocity development. This may mean incurring technical debt to ship a feature as much as it will mean, slowing down developement to build future-proof code, or avoid maintainance burdens later. Always consider the wider context and think about how decisions may affect the team 6 months from now.
📝 Documentation & Comments
If code doesn’t explain itself, future maintainers spend hours “reverse engineering.” Well-placed comments and updated docs keep everyone aligned without digging through git history.
Use JSDoc/TSDoc for public functions & exported hooks
/**
* Fetches user data and returns an object with `id`, `name`, and `email`.
* @param userId - the unique identifier of the user
* @returns Promise<User>
*/
export async function fetchUserData(userId: string): Promise<User> { … }Inline comments for non‐trivial logic
Even, though we should avoid non-trivial logic, it is possible in some cases to have it across the project.
// We loop backwards here because we might remove items mid‐iteration
for (let i = arr.length - 1; i >= 0; i--) {
if (shouldDelete(arr[i])) {
arr.splice(i, 1);
}
}Avoid “noisy” comments
Comments should explain why, not what —the code already shows what it’s doing.
// DON’T
// increment i by 1
i++;
// DO (only if needed)
// Using a reverse loop avoids re‐indexing issues when deleting items
for (let i = arr.length - 1; i >= 0; i--) { … }Performance & Memoization
Small inefficiencies—like calculating a large array on every render—can accumulate into a sluggish UI.
Don’t
// DON’T filter data inline on every render if that data rarely changes
function UserList({ users }: { users: User[] }) {
const activeUsers = users.filter((u) => u.isActive);
// Reruns filter on every parent re-render, even if `users` hasn’t changed
return <ul>{activeUsers.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}Do
// DO memoize expensive calculations
function UserList({ users }: { users: User[] }) {
const activeUsers = useMemo(() => users.filter((u) => u.isActive), [users]);
return <ul>{activeUsers.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}Utilities & Reusability
Don’t
// DON’T copy-paste this “deep clone” logic into multiple files:
function deepClone(obj: any): any {
return JSON.parse(JSON.stringify(obj));
}
// Later, someone else writes a second version:
function cloneObject<T>(source: T): T {
return JSON.parse(JSON.stringify(source));
}
// Now we have two similar utilities—confusion ensues when one needs to be updated.Do
// DO extract (or use) a single reusable helper, then import it everywhere:
import { deepClone } from "@/utils/objectHelpers";
const copyA = deepClone(originalA);
const copyB = deepClone(originalB);
Always search first:
Before creating
myNewHelper.ts, do a quick IDE/global search for existing files named
*.utils.ts,
helpers/*, or the specific functionality (e.g., “clone,” “format,” “calculate”) to see if a matching utility already exists.
Name utilities clearly:
If you’re writing a function that converts a date string into a human-friendly label, name it something like
formatDateToReadableString()and place it under
/utils/dateHelpers.tsThat way, anyone searching for date-formatting logic will find it easily.
If you add a new helper that many other parts of the app will use, drop a short comment or JSDoc at the top of the file explaining its purpose. Example:
