Getting started with Claude Code: a practical guide
# Getting Started with Claude Code: A Practical Guide
I spent three hours last week wrestling with a production bug. The stack trace pointed to a deeply nested async function, and I was manually tracing through each `await` like a detective with a magnifying glass. I finally fixed it, but the whole ordeal made me wonder: *Why am I doing this alone?* That's when I decided to actually learn Claude Code properly—not just as a toy, but as a real pair programmer.
Here's what I found after a week of heavy use, including the parts that broke and the workarounds I discovered.
## What Claude Code Actually Is (And Isn't)
Claude Code is Anthropic's terminal-based AI coding assistant. It's not a VS Code extension that auto-completes your lines. It's a CLI tool that lives in your terminal, reads your entire project context, and can execute shell commands, edit files, and reason about your codebase. Think of it as a junior engineer who's read every line of your code and can run any command—but needs clear instructions and occasional hand-holding.
I tested it on a Next.js project with about 15,000 files (monorepo with apps, packages, and a design system). It handled the context surprisingly well, though I hit the token limit twice on large refactors.
## Installation: The 30-Second Setup
```bash
npm install -g @anthropic-ai/claude-code
# or
yarn global add @anthropic-ai/claude-code
```
Then authenticate:
```bash
claude login
```
That opens a browser window. Paste your API key (get one from console.anthropic.com). Done.
**Pro tip:** If you're on a team, export `ANTHROPIC_API_KEY` in your shell profile so everyone doesn't have to log in individually. We put it in our `.envrc` for direnv users.
## Your First Session: The "Explain This" Command
The most underrated feature is `/explain`. I started with a file that had been bugging me for weeks:
```bash
cd ~/projects/my-app
claude
# Now you're in the interactive REPL
```
I typed:
```
/explain src/utils/data-transformer.ts
```
Claude read the entire file (about 400 lines) and gave me a concise breakdown of the three main functions, the data flow, and—crucially—pointed out a race condition I hadn't noticed. The `Promise.all` call was using a shared mutable array. It even suggested a fix:
```typescript
// Instead of:
const results = [];
for (const item of items) {
results.push(processItem(item));
}
return Promise.all(results).then(() => results);
// It suggested:
const results = await Promise.all(items.map(processItem));
return results;
```
That single interaction saved me a debugging session. The `/explain` command is worth the price of admission alone.
## The "Edit This" Workflow: Where It Shines
Here's the practical workflow I settled on after trial and error:
1. **Describe the problem in natural language** – be specific about the file and the change
2. **Let Claude propose the edit** – it will show you a diff
3. **Review the diff** – I always read it, even if I trust it
4. **Approve or reject** – yes/no per change
Example session:
```
> In src/components/UserCard.tsx, change the loading state from a spinner to a skeleton. Use the existing Skeleton component from ui/skeleton.
```
Claude responded with:
```
I'll read UserCard.tsx and the Skeleton component to understand the current implementation.
[reads files]
Here's the proposed change:
- {loading && <Spinner className="h-8 w-8" />}
+ {loading && <Skeleton className="h-8 w-8 rounded-full" />}
Also, the error state should show a retry button instead of just text. Would you like me to add that too?
```
I said yes, and it added the button with proper click handler. Total time: 45 seconds. Doing it manually: 4 minutes plus context switching.
## The "Fix This Bug" Flow: Real Example
I had a bug where a React component re-rendered infinitely. The stack trace pointed to a `useEffect` dependency array. I pasted the error:
```
> I'm getting "Maximum update depth exceeded" in ProfilePage.tsx. The useEffect at line 42 seems to be the culprit.
```
Claude read the file and said:
```
The issue is on line 44: you're using `user.profile` as a dependency, but `user` is a new object reference every render. The useEffect sets state, which triggers a re-render, which creates a new `user` object, which triggers the effect again.
Fix: Use `user.profile.id` as the dependency instead, or memoize the user object.
```
It then wrote the fix:
```typescript
useEffect(() => {
fetchProfile(user.profile.id);
}, [user.profile.id]); // Changed from [user.profile]
```
This worked perfectly. But here's the catch: Claude didn't check if `user.profile` could be undefined. I caught that in review and added the optional chaining myself. **Always review the output.**
## The "Refactor This" Command: Powerful but Dangerous
I tried Claude's refactoring on a 200-line utility file that had grown organically. My prompt:
```
> Refactor src/utils/format.ts to use a functional pipeline instead of nested if-else. Keep the same public API.
```
Claude proposed a clean pipeline using `pipe` from `lodash/fp`:
```typescript
export const formatDisplayName = pipe(
trim,
capitalize,
truncate(50),
addEllipsis
);
```
Beautiful. But it also renamed one of the internal helper functions (`truncate` → `truncateString`) because it thought the name was ambiguous. That broke three other files that imported it. **Lesson learned:** Always add "Do not change any exported function names or signatures" to refactoring prompts.
## The "Run This Command" Feature: Hidden Gem
Claude can execute terminal commands. This is incredibly useful for:
- Running tests and parsing failures
- Installing dependencies
- Checking git history
Example:
```
> Run the tests for the auth module and show me any failures
```
It ran `npm test -- --testPathPattern=auth`, parsed the output, and showed me exactly which test failed and why. It even suggested the fix:
```
The test at line 34 expects { status: 200 } but the API returns { statusCode: 200 }.
Would you like me to update the test or the API response?
```
## The "Write Documentation" Feature: Actually Good
I needed JSDoc for a complex function. Claude wrote it, including parameter descriptions, return types, and even a usage example:
```typescript
/**
* Transforms raw API response into normalized store shape.
* Handles pagination metadata and error normalization.
*
* @param response - Raw fetch response object
* @param schema - Normalization schema (from schemas/)
* @returns Normalized entity map and result IDs
*
* @example
* const { entities, result } = normalizeResponse(
* await fetch('/api/users'),
* userSchema
* );
*/
```
It even caught that I'd misspelled "normalization" in the original code. Embarrassing, but helpful.
## What Broke (And How I Worked Around It)
### 1. Token Limits on Large Files
Claude maxes out at about 200K tokens of context. My monorepo's `package.json` alone is 80K tokens. I hit the limit twice during a refactor.
**Fix:** Use the `/compact` command to summarize what's been discussed. Or restart the session with a focused prompt: "Continue the refactor of src/utils/format.ts. We were converting nested if-else to functional pipeline."
### 2. Hallucinated File Paths
Twice, Claude suggested edits to files that didn't exist. It invented `src/utils/helpers.ts` when the real file was `src/lib/helpers.ts`.
**Fix:** Always ask "List the files you need to edit first" before letting it make changes. Then verify each path exists.
### 3. Over-Optimistic Refactoring
Claude once suggested replacing a working `for` loop with a recursive function "for readability." The recursive version was elegant but had a stack overflow risk for large arrays.
**Fix:** Add constraints like "Prefer iterative solutions over recursive" or "Maintain O(n) time complexity."
## Practical Tips I Learned the Hard Way
### 1. Use the `/init` Command First
When starting in a new project:
```
> /init
```
This tells Claude to read your `package.json`, `tsconfig.json`, `.eslintrc`, and other config files. It then understands your project's conventions. Without this, it might suggest Prettier formatting that conflicts with your setup.
### 2. Keep Sessions Focused
Each session should have one goal. "Fix the auth bug" not "Improve the codebase." I found that sessions longer than 30 minutes degrade in quality as context accumulates.
### 3. Use the `--verbose` Flag for Debugging
```bash
claude --verbose
```
This shows you the exact prompts being sent to the API. Useful when Claude seems confused—you can see if your instruction was misinterpreted.
### 4. The `--model` Flag Matters
```bash
claude --model claude-3-5-sonnet-20241022
```
The default model is good, but I found Sonnet 3.5 better for code generation and Haiku better for quick explanations. Experiment.
## The Verdict After One Week
Claude Code is not a replacement for understanding your codebase. It's a force multiplier for the boring, repetitive parts of development. I estimate it saved me about 4 hours in my first week—mostly from not having to context-switch between documentation, Stack Overflow, and my editor.
But it's not magic. It hallucinates paths, over-optimizes, and occasionally misunderstands intent. Treat it like a talented intern: give clear instructions, review its work, and never trust it with production credentials.
## Your Next Step
Don't start with a big refactor. Start with `/explain` on a file you've been meaning to understand better. Then try fixing one small bug. Then add one unit test.
The real skill isn't using Claude Code—it's knowing *when* to use it. Use it for the boring stuff. Save your brain for the hard problems.