Skip to content

Instantly share code, notes, and snippets.

@andrioid
Last active February 20, 2026 16:40
Show Gist options
  • Select an option

  • Save andrioid/87d597bfbb8df6df6d5ec3f3c4e0b164 to your computer and use it in GitHub Desktop.

Select an option

Save andrioid/87d597bfbb8df6df6d5ec3f3c4e0b164 to your computer and use it in GitHub Desktop.
Opinionated patterns and principles. Distilled from my PR reviews and enriched manually

Code Patterns & Preferences

General Principles

  • Consistency is paramount. That applies to UI, UX, architecture, code and APIs.
  • We must understand a problem-area before we start work on a solution
  • Aim for simplicity and readability and avoid complexity
  • Humans and computer-agents have limited context-windows. Scope should reflect this, and changes should be applied in digestible phases.
  • Architecture decisions must be reviewed by at least one other engineer
  • Review your own code-changes

Code Style & Structure

  • Prefer functional-style over object-oriented
  • Prefer composability over inheritance
  • We don't do DRY (Don't Repeat Yourself). We do WET (Write Everything Twice). Abstractions need to be warranted.
  • Prefer utility-functions over library dependencies. Check which utilities already exist before creating new ones.
  • When evaluating 3rd party libraries make sure to consider the size of their recursive dependencies as well
  • Dead code must be deleted
  • Look up best-practices, but consider how they fit with how the project is currently structured
  • Return early. Avoid if/elseif/else.
    // Badfunction
    getLabel(status: Status) {  if (status === 'active') {    return 'Active'  } else if (status === 'inactive') {    return 'Inactive'  } else {    return 'Unknown'  }}
    // Goodfunction
    getLabel(status: Status) {  if (status === 'active') return 'Active'  if (status === 'inactive') return 'Inactive'  return 'Unknown'}
  • Aim for "single-responsibility-principle" when possible. Functions should avoid doing multiple unrelated things.
  • Separation of concerns should prioritise business-logic. If there are purely technical concerns, they can be grouped together.
  • Don't rely on undocumented library behaviour.
  • Write idiomatic code. Use modern-syntax and established patterns for the given language.
  • Code-style should be handled by automatic formatters, not debated in reviews
  • Ternary usage should never span more than 2 lines. If they do, it's a code-smell indicating we need to extract some code.
  • Immediately invoked function expressions are valid for computed values, but should otherwise be avoided
  • Treat deprecations as warnings and suggest fixes
  • It's good to extract business-logic into services. Group the services by domain-entities.

Naming

  • Use descriptive names that use business-domain wording where applicable
  • Length of variable names should be longer/shorter depending on their scope. Examples below:
    • Global variables or functions const navigationMenuItems = [] refers to the place where it's being used and what the purpose is
    • Modules can omit the module scope, but explain purpose. E.g. in a navigation.ts module, const menuItems = [] where the purpose is explained, but module name is omitted.
    • Functions can omit the function scope, but explain purpose.
    • Avoid single character variable names, except where it's idiomatic to do so.
  • Filenames must be lower-case. Not all file-systems are case-sensitive and this can cause issues.
  • Use snake-case

Comments & Documentation

  • Comments should be added when they add value. Not to re-iterate the name of functions or variables.
  • Comments must explain why, not what the code means
    // Bad: re-states the code
    // Set the border width to 15
    const BORDER_WIDTH = 15;
    // Good: explains the reasoning
    // Must match the design token in Figma (updated 2024-12)
    const BORDER_WIDTH = 15;
  • Magic constants (e.g. const BORDER_WIDTH = 15px) must have a comment explaining where this magic constant comes from and why it's required
  • Formatting should be handled by utility functions and only overwritten when necessary. When overwritten a comment should explain the use-case warranting it
  • Label functions with good patterns with a comment @goodpattern [short argument for why it's a good pattern]
  • If you make signifigant changes that affect our documentation. Then you must update the documentation.
  • All decisions are documented with a very short and concise description in DECISIONS.md. Each decision has a pullet-point, a date, the git-email of who made the decision and a quick summary.
  • Never add comments for sections. E.g. "styles", "implementation". That's noise.

Type Safety & Validation

  • We must write type-safe code when using a type-safe language
  • For data-models that span across boundaries (such as REST/API/RPC), use schemas
  • Backend must validate user-input
  • Frontend should validate user-input, preferably with a schema from the backend

Security

  • Be aware of security implications of code
  • Backend must validate user-input

TypeScript

  • Never mix async/await with .then() syntax. Use one or the other.
  • Don't use async function() when the function doesn't return a promise(like) response
  • Types should be inferred where possible. Explicitly set when necessary.
  • Prefer unknown over any. Only use any where it's absolutely necessary.
  • Never type-cast (e.g. as SomeType) unless necessary. Prefer satisfies SomeType.
    // Bad: silently discards type errors
    const config = { timeout: "500" } as Config;
    // Good: validates the shape while preserving the narrower type
    const config = { timeout: 500 } satisfies Config;
  • Don't group exports with barrel files. It messes with tooling and can cause files to be initialised in the wrong order.
  • Use Path-aliases. Never use ../file. Use ~/ where the ~/ refers to the project-root.
    // Bad
    import { formatDate } from "../../../utils/dates";
    // Good
    import { formatDate } from "~/utils/dates";

Runtime Validation

  • Use Zod or other standard-schema compatible library
  • If there's overlap between TypeScript types and a schema definition; then infer the type from the schema type
    // Bad: duplicates the shape, can drift out of sync
    const UserSchema = z.object({ name: z.string(), age: z.number() })type User = { name: string; age: number }
    // Good: single source of truth
    const UserSchema = z.object({ name: z.string(), age: z.number() })type User = z.infer<typeof UserSchema>

Testing

  • Tests are for behaviour, not implementation specifics
  • Avoid writing redundant tests
  • Use test-matrices where applicable
  • When implementing new features with non-trivial behaviours, write a unit-test first
  • When refactoring old features with non-trivial behaviours, write a unit-test first

Fast Feedback

Verify your work in the fastest way possible. Or, in order:

  1. Check syntax, using editors or language-server-protocols (LSP)
  2. Check compiler output and address any errors.
  3. Check linter output. Address both warnings and errors.
  4. Run Unit-tests
  5. If the changes are web-UI, use Playwright to test the feature
  • When working with APIs use curl to test if the API is responding in the expected way
  • If an API has schema, validate against the schema

Architecture

  • Business logic should live on the backend, when possible
  • Prefer server-rendering over client-side JavaScript

UI & UX

  • Roundness, spacing, colours and font-usage should be consistent across the solution and adhere to the design system
  • UI must look and feel consistent across the project
  • User feedback is critical.
  • If an operation is pending, it must be visible to the user. Prefer skeleton loaders, but spinners are also acceptable
  • If an operation failed, it must be visible to the user
  • If there was a validation error of user-input the error must be visible to the user
  • Interactions that expect user attention should use animations
  • Animations should be whimsical, subtle and not distracting
  • Use CSS for animations and interactions over JavaScript where applicable
  • Modals should not have modals
  • Be aware of device margins and available view-port space
  • If you make changes to translation keys, you must update translation files

Accessibility

  • Users with screen readers should be able to use our solution
  • Users who prefer reduced motion should not be shown animations
  • Prefer using the web-platform (HTML/CSS) over framework specific solutions

Styling

  • Use Tailwind v4 and look at similar components and our theme to get a feel for how it should look
  • Gradually build out a design-system and extract our CSS into logical-files using @import where applicable.
  • When class-names exceed ~80 characters, extract commonly used patterns into Tailwind utilities
  • When we need animations, use transitions or animations
  • Survey existing key-frames before adding a new one
  • Don't use CSS features that are not "generally available" in mainline browsers. You can check https://caniuse.com to see how it's supported.

React

  • Hook dependencies should not change during render. E.g. new Date() creates a new object every render, causing infinite re-renders.
    // Bad: new object every render
    const [start] = useState(new Date());
    useEffect(() => {
       fetch(`/api?since=${start}`);
    }, [start]);
    // Good: stable value
    const [start] = useState(() => new Date());
    useEffect(() => {
       fetch(`/api?since=${start}`);
    }, [start]);
  • Hook order must be consistent between re-renders. See React's rule of hooks.
  • Never put multiple components in one file
  • Never place multi-conditional rendering logic into JSX. Extract it into logical variables above the JSX instead.
  • UI components should never use useEffect directly. Create a custom hook for the use-case instead.
  • When a useMemo implementation is longer than 5 lines, move it into a custom hook.

React Native

  • Use ternary with null instead of && for conditional rendering in React Native to avoid accidental text node rendering.

State Management (best-to-worst)

  1. Avoid state where possible. E.g. view-components or where letting HTML elements handle it is sufficient
  2. Components may use useState() when it is necessary for the component to own the data being stored and the data stored is relatively simple.
    • When the complexity of state increases try to extract functionality into logical sub-components
    • If that's not possible, try useReducer()
  3. Page components should use page parameters, and URL state for state that belongs to the page
  4. Context can be used when we are prop-drilling (passing same props multiple levels of components) but otherwise avoided
  5. Global state should be avoided, and only be used when multiple areas of the system are modifying the same state. Consider state-management libraries, state-machines or state-charts where this is necessary.

GraphQL

  • Filters should affect the level where they are applied. Sub-queries should have their own filters
  • The graph should represent a business domain and use wordings from the business domain
  • The graph should be traversable. E.g. using sub-entities instead of simply listing sub-entity-ids

Cross Platform Apps

  • Be aware of how platform quirks can create bugs for other platforms. Create platform specific wrappers when necessary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment