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
npm install -g @anthropic-ai/claude-code
# or
yarn global add @anthropic-ai/claude-code
Then authenticate:
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:
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:
// 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:
- Describe the problem in natural language – be specific about the file and the change
- Let Claude propose the edit – it will show you a diff
- Review the diff – I always read it, even if I trust it
- 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:
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:
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:
/**
* 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
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
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.