How to use Cursor for coding

codingbeginner6 min read6/4/2026

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:

// 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:

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:

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:

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:

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

Related Agent

C

Claude Code

Anthropic's AI-powered coding agent that helps you write, edit, and review code

Read more →