You just joined a new company. The repo has 2,000 files, 150,000 lines of code, and zero documentation that explains how anything actually works.
Welcome to every JavaScript developer's first week.
Most developers approach a new codebase by randomly clicking through files hoping something makes sense. This is slow, frustrating, and leaves gaps in understanding that cause problems months later.
There is a better way. A systematic approach that gives you a working mental map in days, not weeks.
Start With package.json
Before opening any code file, read package.json. This is the table of contents for the entire project.
{
"name": "company-app",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test": "vitest",
"lint": "eslint . --ext ts,tsx"
},
"dependencies": {
"react": "^18.2.0",
"react-router-dom": "^6.8.0",
"@tanstack/react-query": "^4.24.0",
"zustand": "^4.3.0",
"axios": "^1.3.0"
}
}
From this single file you now know:
Build tool: Vite (not Webpack, not Create React App)
Language: TypeScript (tsc in build script)
Testing: Vitest (not Jest)
Routing: React Router v6
Data fetching: React Query
State management: Zustand (not Redux)
HTTP client: Axios (not fetch)
You have not opened a single component yet, but you already know the major architectural decisions.
Map the Folder Structure
Run this command to see the high level structure:
find . -type d -maxdepth 3 | grep -v node_modules | grep -v .git | head -50
Or use tree if installed:
tree -L 3 -d -I 'node_modules|.git|dist|coverage'
Common patterns you will see:
src/
├── components/ # Reusable UI components
├── pages/ # Route components
├── hooks/ # Custom hooks
├── utils/ # Helper functions
├── services/ # API calls
├── stores/ # State management
├── types/ # TypeScript types
└── assets/ # Images, fonts
Or the feature based structure:
src/
├── features/
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── api.ts
│ ├── dashboard/
│ └── settings/
├── shared/
└── app/
Understanding which pattern the codebase uses tells you where to look for things.
Find the Entry Point
Every application has a starting point. Find it.
For React apps, check index.html for the script tag, then trace to the main entry:
cat index.html | grep -i script
Usually leads to src/main.tsx or src/index.tsx:
// src/main.tsx
import { App } from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
)
This shows you the provider hierarchy. Here you can see React Query and React Router wrapping the app.
Trace One Complete Flow
Do not try to understand everything. Pick one feature and trace it from user interaction to database and back.
Step 1: Find a route
Look for the router configuration:
grep -r "Route\|createBrowserRouter" src/ --include="*.tsx" | head -20
Find something simple like a login page or settings page.
Step 2: Open that page component
// src/pages/Settings.tsx
export function Settings() {
const { data: user } = useUser()
const updateUser = useUpdateUser()
return (
<form onSubmit={(e) => {
e.preventDefault()
updateUser.mutate(formData)
}}>
{/* form fields */}
</form>
)
}
Step 3: Find the hook definition
grep -r "useUser\|useUpdateUser" src/ --include="*.ts" --include="*.tsx" | head -10
// src/hooks/useUser.ts
export function useUser() {
return useQuery({
queryKey: ['user'],
queryFn: () => api.get('/user').then(res => res.data)
})
}
export function useUpdateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: UserUpdate) => api.patch('/user', data),
onSuccess: () => queryClient.invalidateQueries(['user'])
})
}
Step 4: Find the API configuration
grep -r "axios.create\|baseURL" src/ --include="*.ts" | head -5
// src/services/api.ts
export const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: { 'Content-Type': 'application/json' }
})
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
You have now traced a complete flow: UI component → custom hook → React Query → Axios → API. You understand how data moves through this application.
Use Git History as Documentation
The codebase has a hidden documentation source: git history.
Find recently changed files:
git log --oneline --name-only -20 | grep "\.tsx\|\.ts" | sort | uniq -c | sort -rn | head -10
Files that change often are the active areas of development. Start there.
Understand why code exists:
git log -p --follow -S "someFunction" -- "*.ts"
This shows every commit that added or removed "someFunction". You can see when it was introduced and why.
Find who knows what:
git shortlog -sn -- src/features/auth/
This shows who has committed most to the auth feature. They are the person to ask questions.
Read the PR that introduced a file:
On GitHub, open any file and click "History" then "View pull request" on commits. The PR description often explains the reasoning that is missing from the code itself.
Search Strategically
Random grep is inefficient. Use targeted searches.
Find all API endpoints:
grep -rn "api\.\(get\|post\|put\|patch\|delete\)" src/ --include="*.ts"
Find all routes:
grep -rn "path:\|<Route" src/ --include="*.tsx"
Find state management:
# For Zustand
grep -rn "create(" src/stores/ --include="*.ts"
# For Redux
grep -rn "createSlice\|useSelector\|useDispatch" src/ --include="*.ts"
Find environment variables:
grep -rn "process.env\|import.meta.env" src/ --include="*.ts" --include="*.tsx"
Find all custom hooks:
ls src/hooks/
# or
grep -rn "^export function use" src/ --include="*.ts"
Build a Mental Map
As you explore, document what you find. A simple markdown file is enough:
# Codebase Map
## Tech Stack
- React 18 + TypeScript
- Vite for bundling
- React Query for server state
- Zustand for client state
- React Router v6
## Key Files
- src/main.tsx - Entry point, providers setup
- src/App.tsx - Main routes
- src/services/api.ts - Axios instance, interceptors
- src/stores/authStore.ts - Auth state, token handling
## Data Flow
1. Components use custom hooks (src/hooks/)
2. Hooks use React Query for server data
3. React Query calls API functions (src/services/)
4. API functions use configured Axios instance
## Auth Flow
1. Login form calls useLogin hook
2. Hook calls POST /auth/login
3. Response token stored in localStorage
4. Axios interceptor attaches token to requests
5. authStore.isAuthenticated derived from token presence
## Questions to Ask
- Why is there both Zustand and React Query?
- What is the deployment process?
- Where are feature flags configured?
This document becomes invaluable when you need to onboard another developer or when you return to an unfamiliar part of the codebase months later.
Run the Tests
Tests are executable documentation.
npm test -- --watch
Open the test files alongside the implementation:
// src/hooks/useUser.test.ts
describe('useUser', () => {
it('fetches current user on mount', async () => {
server.use(
rest.get('/api/user', (req, res, ctx) => {
return res(ctx.json({ id: 1, name: 'John' }))
})
)
const { result } = renderHook(() => useUser())
await waitFor(() => {
expect(result.current.data).toEqual({ id: 1, name: 'John' })
})
})
})
Tests show you the expected behavior and edge cases that are not obvious from reading the implementation.
Debug to Understand
Sometimes reading is not enough. Run the code with breakpoints.
Add a debugger statement:
function useUser() {
debugger // Execution pauses here
return useQuery({
queryKey: ['user'],
queryFn: () => api.get('/user')
})
}
Or use React DevTools and React Query DevTools to inspect state without modifying code.
For understanding render cycles:
useEffect(() => {
console.log('Component rendered', { props, state })
})
Remove these before committing. But while learning, they are invaluable.
Common Patterns to Recognize
JavaScript codebases tend to use recognizable patterns. Spotting them speeds up comprehension.
Container and Presentational Components:
// Container (has logic)
function UserProfileContainer() {
const { data: user } = useUser()
return <UserProfile user={user} />
}
// Presentational (pure UI)
function UserProfile({ user }: { user: User }) {
return <div>{user.name}</div>
}
Custom Hooks for Logic Extraction:
function useForm(initialValues) {
const [values, setValues] = useState(initialValues)
const [errors, setErrors] = useState({})
const handleChange = (e) => {
setValues(prev => ({ ...prev, [e.target.name]: e.target.value }))
}
return { values, errors, handleChange }
}
API Layer Abstraction:
// src/services/users.ts
export const usersApi = {
getAll: () => api.get('/users'),
getById: (id: string) => api.get(`/users/${id}`),
create: (data: CreateUser) => api.post('/users', data),
update: (id: string, data: UpdateUser) => api.patch(`/users/${id}`, data),
delete: (id: string) => api.delete(`/users/${id}`)
}
Compound Components:
<Select>
<Select.Trigger>Choose option</Select.Trigger>
<Select.Content>
<Select.Item value="1">Option 1</Select.Item>
<Select.Item value="2">Option 2</Select.Item>
</Select.Content>
</Select>
When you recognize a pattern, you do not need to read every line. You know what to expect.
Tools That Help
VS Code extensions:
GitLens - See who changed each line
Error Lens - Inline error display
Pretty TypeScript Errors - Readable type errors
Import Cost - Shows package sizes
CLI tools:
# Count lines by file type
find src -name "*.tsx" | xargs wc -l | tail -1
# Find largest files
find src -name "*.ts" -exec wc -l {} + | sort -rn | head -10
# Find TODO comments
grep -rn "TODO\|FIXME\|HACK" src/ --include="*.ts" --include="*.tsx"
Browser extensions:
React Developer Tools
React Query Devtools
Redux DevTools (if using Redux)
The First Week Checklist
Day 1:
- [ ] Read package.json completely
- [ ] Map folder structure
- [ ] Find and read entry point
- [ ] Run the app locally
Day 2:
- [ ] Trace one simple feature end to end
- [ ] Find API configuration
- [ ] Understand auth flow
Day 3:
- [ ] Run test suite
- [ ] Read tests for one feature
- [ ] Find the CI/CD configuration
Day 4:
- [ ] Search git history for active areas
- [ ] Identify who knows what
- [ ] Start your mental map document
Day 5:
- [ ] Pick up your first ticket
- [ ] Apply what you learned
- [ ] Note what is still confusing
Every codebase feels overwhelming on day one. By day five, with a systematic approach, you have a working mental map that lets you contribute confidently.
The developers who ramp up fast are not smarter. They just have better systems for extracting understanding from chaos.
Now go read some code.
Top comments (0)