Getting started with Claude Code: a practical guide

codingbeginner

# 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.