Code Writing

The Proximity Principle

A visual guide to structuring code and files.
Less jumping, more shipping.

The Core Principles

  • Colocate

    Put related code as close as possible to where it's used.

  • Inline First

    Don't extract until code is reused. Keep it inline by default.

  • Lowest Common Ancestor

    When sharing code, place it at the nearest common parent directory.

  • Prefer Fewer Files

    One file with related code beats many files requiring imports.

Don't Abstract Prematurely

If code is only used once, keep it inline. Abstraction adds complexity.

Over-abstracted
Separate file for code used only once. Extra import, extra cognitive load.
Inline
Code lives where it's used. No imports, no jumping between files.

Exception: React State Isolation

Extract a child component when it has its own state — even if only used once. This prevents the parent from re-rendering when the child's state changes.

State Isolation Pattern
The child owns the state, so typing only re-renders the input — not the entire parent tree.

Prefer Fewer Files

Splitting code across many files increases cognitive load. Keep related code together.

Bad
index.tsx
UserProfile.tsx
UserProfile.styles.ts
UserProfile.types.ts
UserProfile.hooks.ts
5 files for one component. Too many imports, too much jumping.
Good
user-profile.tsx
One file. Everything related is together.

Extract Only When Reused

Only move code to a separate file when it's needed in multiple files.

Premature extraction
Extracted to separate file even though only used in one place.
Same file
Hook stays in the file where it's used. Easier to find and modify.

Lowest Common Ancestor

When you do share code, place it at the nearest common parent.

Tip: Avoid generic directory names like features/. Use specific names that describe what the directory contains: pages/, routes/, controllers/, etc.

Bad
format-date.ts
user-profile.tsx
profile-header.tsx
user-activity.tsx
format-date.ts looks like a page. Misleading without a directory to clarify its purpose.
Good
format-date.ts
user-profile.tsx
profile-header.tsx
user-activity.tsx
A utils directory clarifies intent. Still at the lowest common ancestor.

Exception: Global Code

For something that we can expect to be used globally (e.g. a button component) or used almost everywhere, it's better to put them in a "type" directory (e.g. components/, schemas/).

Bad
button.tsx
user-profile.tsx
settings-page.tsx
dashboard.tsx
Button is used everywhere, but placed at the lowest ancestor. It doesn't belong to any page.
Good
button.tsx
user-profile.tsx
settings-page.tsx
dashboard.tsx
Globally used code lives in a type directory. Clear intent, easy to find.

Follow Framework Rules

File-based routers (Next.js, TanStack Router) often turn every file into a route. Co-location requires understanding how to "hide" files.

Bad
posts.tsx
post-header.tsx
Default behavior: 'post-header' becomes a public route (`/blog/post-header`). Ouch.
Good
posts.tsx
post-header.tsx
Using ignored folders (via config or convention like `-components`) keeps files safe.

File Suffixes

When organizing by type at scale, use suffixes for discoverability.

Organized Structure
user.schema.ts
org.schema.ts
use-auth.hook.ts
use-theme.hook.ts
Suffixes help with VS Code's 'Go to File' (Ctrl+P). No more guessing!

Quick Reference

1

Code used onceKeep it inline

2

Code is too longExtract to function, keep in file

3

Code reused in same fileExtract to function, keep in file

4

Code reused across filesExtract to lowest common ancestor

5

Code used globally / almost everywherePut in a type directory (e.g. components/, schemas/)

6

React: Child has own stateExtract to component (prevents parent re-render)

Why This Matters

  • 🧠Reduces cognitive load — Less jumping between files
  • 🚀Faster shipping — Less time spent managing file structure
  • 🧹Easier cleanup — Delete a file and everything goes with it