How to use Cursor for coding

codingbeginner

# I Spent 30 Days Coding with Cursor—Here's What Actually Works

You know that feeling when you're staring at a red squiggly line under a TypeScript error you've seen a hundred times, and you still can't remember the exact syntax for `Omit` with nested generics? That was me three months ago, until I started using Cursor, and I'm never going back to plain VS Code for complex projects.

But here's the thing: Cursor isn't magic. I spent my first week treating it like a glorified autocomplete, and I got garbage results. After 30 days of deliberate practice, I've found the patterns that actually save time—and the ones that waste it.

## The Setup That Matters

First, let's get the basics right. I'm running Cursor 0.45.x on macOS, but the same principles apply on Windows/Linux. After installing, I immediately changed two settings that most tutorials skip:

1. **Disable "Auto Completions" for inline suggestions** (Settings > Editor > Cursor > Completions: Off). The constant ghost text was making me second-guess every keystroke. Instead, I use `Ctrl+K` to trigger inline generation only when I want it.

2. **Enable "Apply Edits" mode** (Cmd+Shift+P > "Cursor: Toggle Apply Edits"). This lets Cursor modify your code directly rather than just showing a diff. Huge time saver.

## The 80/20 Rule: Two Commands You'll Actually Use

After 30 days, I've boiled Cursor down to two commands that handle 80% of my daily work:

### 1. `Cmd+K`: The "Fix This Block" Command

This is your bread and butter. Select a block of code, hit `Cmd+K`, and tell Cursor what to do. Here's a real example from a React project I'm building:

```typescript

// I selected this mess I wrote:

const handleSubmit = async (e) => {

e.preventDefault()

const data = new FormData(e.target)

const body = {}

for (let [key, val] of data.entries()) {

body[key] = val

}

// ...50 more lines of manual validation...

}

```

I typed: "Refactor to use react-hook-form with zod validation, handle loading states, and add error boundaries"

Cursor generated this in 4 seconds:

```typescript

const schema = z.object({

email: z.string().email(),

password: z.string().min(8),

})

type FormData = z.infer<typeof schema>

const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({

resolver: zodResolver(schema)

})

const onSubmit: SubmitHandler<FormData> = async (data) => {

try {

setError(null)

await api.login(data)

toast.success('Logged in!')

} catch (err) {

setError(err instanceof Error ? err.message : 'Login failed')

}

}

```

**The trick**: Be specific about the library and patterns you want. "Refactor" alone gives you generic code. "Refactor to use react-hook-form with zod" gives you production-ready code.

### 2. `Cmd+L`: The "Explain This" Command

This is secretly the most powerful feature. I use it constantly when I'm working with unfamiliar codebases or legacy spaghetti.

Example: I was debugging a Python script that used `__getattr__` in a way I couldn't follow:

```python

class ConfigProxy:

def __init__(self, config):

self._config = config

def __getattr__(self, name):

if name.startswith('_'):

return super().__getattr__(name)

return self._config.get(name, None)

```

I highlighted it and asked: "Explain this pattern, and show me how to type-hint it properly for Pyright"

Cursor's response was surprisingly thorough—it explained the proxy pattern, pointed out that `__getattr__` would cause infinite recursion on missing attributes, and gave me a typed version with `Protocol`:

```python

from typing import Any, Protocol

class ConfigProtocol(Protocol):

def get(self, key: str, default: Any = None) -> Any: ...

class TypedConfigProxy:

def __init__(self, config: ConfigProtocol):

self._config = config

def __getattr__(self, name: str) -> Any:

if name.startswith('_'):

raise AttributeError(name)

return self._config.get(name, None)

```

**The trick**: Always ask for "type hints" or "proper typing" if you're in a typed language. Cursor defaults to untyped code unless you ask.

## The Pattern That Changed My Workflow

Here's what I do now for any new feature:

1. **Write a comment describing the function** (not the implementation)

2. **Highlight the comment** and press `Cmd+K`

3. **Review the generated code** carefully—I always find at least one edge case it missed

4. **Iterate with follow-up prompts** like "Add error handling for network failures" or "Make this handle empty arrays"

Example from a recent project:

```typescript

// I wrote this comment:

// Function that fetches user data, caches it for 5 minutes,

// and handles stale-while-revalidate pattern

// Cursor generated this (after 3 iterations):

const userCache = new Map<string, { data: User; timestamp: number }>()

const CACHE_TTL = 5 * 60 * 1000

async function getUser(id: string): Promise<User> {

const cached = userCache.get(id)

const isStale = cached && Date.now() - cached.timestamp > CACHE_TTL

if (cached && !isStale) {

return cached.data

}

// Stale-while-revalidate: return stale data immediately, refresh in background

if (cached && isStale) {

fetchUser(id).then(data => {

userCache.set(id, { data, timestamp: Date.now() })

})

return cached.data

}

const user = await fetchUser(id)

userCache.set(id, { data: user, timestamp: Date.now() })

return user

}

```

## Where Cursor Fails (And What to Do Instead)

I've hit three major pain points:

**1. It hallucinates APIs.** Cursor once generated code using a `useMemo` hook with a dependency array that didn't exist in the React version I was using. **Fix**: Always verify generated code against your actual dependencies' docs.

**2. It's terrible at large refactors.** I tried asking it to "convert this Redux store to Zustand" across 5 files. It created a mess of inconsistent imports and missing state slices. **Fix**: Do refactors one file at a time, with explicit instructions about the target pattern.

**3. It struggles with project-specific conventions.** My team uses `snake_case` for database columns but `camelCase` in TypeScript. Cursor consistently generates `camelCase` everywhere. **Fix**: Add a system prompt (`.cursorrules` file in project root) with your conventions:

```

# .cursorrules

- Use camelCase for TypeScript variables

- Database column names use snake_case

- Prefer async/await over .then()

- Error handling: always use try/catch with typed errors

```

## The Real Productivity Hack

The biggest time saver isn't code generation—it's **debugging assistance**. When I get a cryptic error message, I copy it into Cursor's chat (`Cmd+L`) along with the relevant code block. It's saved me hours on:

- **TypeScript type errors**: "Type 'X' is not assignable to type 'Y'" → Cursor explains the type mismatch and suggests fixes

- **React render issues**: "Maximum update depth exceeded" → Cursor identifies the infinite loop in my useEffect dependencies

- **API integration bugs**: "Cannot read properties of undefined" → Cursor traces through async code to find the null reference

## Your Next Step

Don't try to use all of Cursor's features at once. Here's your 7-day plan:

**Day 1-2**: Only use `Cmd+K` for inline code generation. No chat, no explain, no composer.

**Day 3-4**: Add `Cmd+L` for debugging and explaining unfamiliar code. Still no composer.

**Day 5-7**: Add `.cursorrules` to your project and try the composer for multi-file edits.

After 7 days, you'll have muscle memory for the patterns that actually work. And when you inevitably hit a hallucination or a bad refactor, you'll know exactly when to trust Cursor and when to write the code yourself.

Now go open that file you've been avoiding, highlight the ugliest function, and hit `Cmd+K`. Your future self will thank you.