Contact

What I Learned Coding in 2025

December 2, 2025
Nick Paolini
11 min read
ReflectionLearningCareerPersonal Growth
What I Learned Coding in 2025

Back in November, I committed to learning in public. Now as 2025 winds down, I want to look back at what this year actually taught me. Not the polished LinkedIn version - the real lessons, including the stuff that didn't work.

The Big Technical Wins

1. Finally "Getting" TypeScript

I've used TypeScript for a couple years, but this year something clicked. I stopped fighting the type system and started leveraging it.

The turning point was understanding discriminated unions and template literal types. Before, I'd use any when types got complicated. Now I know how to model complex state properly:

// Old me: this works but loses type safety
type State = {
  loading: boolean
  error?: string
  data?: User
}
 
// Current me: impossible states are actually impossible
type State =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'error'; error: string }
  | { status: 'success'; data: User }

This pattern has eliminated entire categories of bugs from my code. TypeScript went from "nice to have" to "genuinely makes me more productive."

2. React Server Components (Finally Made Sense)

I'll be honest - RSC confused the hell out of me for months. The mental model of server vs client components, when to use what, how data flows... it felt unnecessarily complex.

Then I built a small project with Next.js 14 App Router and it clicked. The "aha moment" was realizing: default to server components, only go client when you need interactivity.

// Server component - default, no 'use client' needed
async function BlogPost({ slug }: { slug: string }) {
  const post = await db.posts.findBySlug(slug) // Direct DB access!
 
  return (
    <article>
      <h1>{post.title}</h1>
      <LikeButton postId={post.id} /> {/* Client component */}
    </article>
  )
}
 
// Client component - only when needed
'use client'
function LikeButton({ postId }: { postId: string }) {
  const [liked, setLiked] = useState(false)
  // Interactive state lives here
}

The performance benefits are real. My blog loads instantly now because most of the page is server-rendered. I'm a convert.

3. Command Line Tools in Rust

I built a Pomodoro timer CLI in Rust this year (wrote about it here). Learning Rust was humbling - the borrow checker fought me constantly at first.

But once it clicked, I realized: the compiler is catching bugs that would be runtime errors in JavaScript. No more "undefined is not a function" surprises.

The best part? CLI tools in Rust are fast and have zero dependencies. I can ship a single binary that just works. No "install Node first" instructions.

The Mistakes That Taught Me the Most

1. Over-Engineering a Side Project to Death

I started a project management tool in January. By March, I had:

  • Custom design system
  • Comprehensive component library
  • Full test suite
  • CI/CD pipeline
  • Database migration system
  • Feature flags
  • Analytics integration

And zero users. Not even me - I never actually used it.

Lesson: Build the thing first, add infrastructure later. Start with the MVP, validate the idea, then scale up. I wasted months building the perfect foundation for something nobody wanted.

Next project, I shipped a working prototype in a weekend. It was ugly and had bugs, but it was real and people could use it.

2. Not Learning Testing Properly Sooner

I used to write tests after the fact, if at all. This year, I forced myself to write tests first for a few features.

At first, it felt slow and awkward. But then I noticed: the code I wrote test-first was better designed. Having to test it forced me to think about interfaces and dependencies upfront.

Example: I was building a payment integration. Instead of tightly coupling to Stripe:

// Tightly coupled - hard to test
async function processPayment(amount: number, cardToken: string) {
  const stripe = new Stripe(process.env.STRIPE_KEY)
  return await stripe.charges.create({ amount, source: cardToken })
}
 
// Testable - dependency injection
interface PaymentProvider {
  charge(amount: number, token: string): Promise<PaymentResult>
}
 
async function processPayment(
  amount: number,
  cardToken: string,
  provider: PaymentProvider
) {
  return await provider.charge(amount, cardToken)
}

Now I can test payment logic without hitting Stripe's API. And if I switch providers later, only one place changes.

Lesson: Tests aren't just for catching bugs - they're design feedback.

3. Chasing the New Shiny Thing

I tried:

  • Bun (went back to Node)
  • A new CSS framework every month
  • Three different state management libraries
  • Two different bundlers
  • Four editor themes

Most of these changes didn't actually improve my productivity. They just gave me something new to tinker with while avoiding real work.

Lesson: Boring technology is often the right choice. Master what you have before chasing the new hotness.

The exception: I switched from Create React App to Vite, and that was worth it. Build times dropped from 30 seconds to 2 seconds. Sometimes the new thing really is better.

Tools and Workflows That Actually Improved My Life

1. Cursor / AI Coding Assistants

I was skeptical about AI coding tools. "It'll make you dumber," "you won't learn," etc.

But then I tried Cursor for a month, and... it's legitimately helpful? Not for everything, but for specific tasks:

Where AI excels:

  • Boilerplate code (API routes, CRUD operations)
  • Writing tests for existing code
  • Refactoring and renaming across files
  • Converting designs to code
  • Explaining unfamiliar code

Where AI fails:

  • Architecture decisions
  • Complex business logic
  • Debugging subtle bugs
  • Knowing what not to build

I use it like a very fast junior developer. It handles the tedious stuff while I focus on the interesting problems.

Real example: I needed to add authentication to a project. AI generated the entire auth flow (login, signup, session management) in minutes. I reviewed it, made adjustments, and shipped. Would've taken me hours otherwise.

2. Better Git Habits

I finally internalized good Git hygiene this year:

  • Small, focused commits
  • Conventional commit messages (feat:, fix:, etc.)
  • Feature branches for everything
  • Rebasing to keep history clean

The payoff: When a bug appeared, I could bisect commits to find exactly where it was introduced. When a feature needed reverting, I could target the specific commits.

Pro tip: git log --oneline is way more useful when your messages are descriptive.

3. Pomodoro Technique (Actually Works)

I built that Pomodoro CLI and started actually using it. 25 minutes of focused work, 5 minute break. Repeat.

Turns out, my "productive coding sessions" where I worked for 4 hours straight were mostly just scrolling Twitter and pretending to work. With Pomodoro, I get more done in 2 focused hours than in 4 distracted ones.

Side benefit: The forced breaks helped my RSI issues. Turns out, standing up occasionally is good for you.

The Mindset Shifts

1. Shipping Beats Perfection

My best work this year was the stuff I shipped quickly and iterated on. My worst work was the stuff I polished forever and never released.

Perfect code that nobody uses is worthless. Imperfect code that solves someone's problem is valuable.

2. "I Don't Know" is a Valid Answer

I used to fake confidence when I didn't understand something. This year I started saying "I don't know, let me research that" instead.

Turns out, admitting ignorance and then figuring it out is way more valuable than pretending to know everything. People respect honesty.

3. Learning in Public Works

Writing blog posts about what I'm learning has been surprisingly valuable:

  • Clarifies my thinking - If I can't explain it simply, I don't understand it
  • Creates opportunities - Had two freelance gigs come from blog posts this year
  • Builds confidence - Seeing my progress over time is motivating
  • Helps others - Multiple people reached out to say a post helped them

The scary part (hitting publish) gets easier each time.

Projects I'm Proud Of

1. This Blog

Rebuilt my personal site with Next.js 14, Tailwind, and MDX. It's fast, looks good, and writing posts is actually enjoyable now.

Traffic is still small, but it's growing. More importantly, I have a record of my learning journey.

2. Pomodoro CLI

Small project, but I use it every day. It solved a real problem for me and taught me Rust.

Open sourced it too - got my first external contribution from someone I've never met. That felt good.

3. Client Projects

Shipped three major client projects this year:

  • E-commerce site for a local business (Next.js + Stripe)
  • Dashboard for a SaaS product (React + D3.js for data viz)
  • Marketing site with CMS (Next.js + Sanity)

All are live, all are making money for the clients. That's the real metric.

What I'm Leaving Behind in 2025

Tutorial hell - I don't need another React course. I need to build things.

Perfectionism - Good enough shipped beats perfect in progress.

Comparison - Someone will always be better/faster/smarter. That's fine. I'm competing with yesterday's version of me.

Imposter syndrome - Or at least, not letting it stop me from shipping.

Saying yes to everything - I overcommitted this year. Need to be more selective in 2026.

What I'm Carrying Into 2026

Technical Goals

Learn Go - Want to build backend services. Go seems like the right tool.

Master testing - Write a comprehensive test suite for a real project. Unit, integration, e2e - the whole stack.

Contribute to open source - Beyond tiny typo fixes. Want to make a meaningful contribution to a project I use.

Build a SaaS - Ship something people pay for. Not for the money, but to understand the full stack of building a business.

Content Goals

Weekly blog posts - Even if they're short. Consistency over perfection.

Video content - Maybe? Writing feels natural, but video might be worth exploring.

More "building in public" - Tweet progress, share what I'm working on, be more visible.

Personal Goals

Better work-life balance - I coded almost every evening this year. Need to protect personal time.

Physical health - My back hurts from sitting too much. Exercise can't be optional anymore.

Deeper, not wider - Master a few things instead of being mediocre at many.

Advice to Past Me (January 2025)

  1. Ship that side project in week one - You'll spend 6 months perfecting it and never launch. Just ship the MVP.

  2. Write tests from the start - Future you will be grateful.

  3. That framework you're excited about? - It won't matter. Focus on fundamentals.

  4. Learn TypeScript properly - Stop using any everywhere. The type system is your friend.

  5. Say no more - You'll burn out saying yes to everything. Protect your time.

  6. Start the blog earlier - You'll wish you had started writing months ago.

  7. Ask for help sooner - That bug you spent 8 hours on? Someone on Discord could've helped in 10 minutes.

The Honest Truth

2025 was a good year for my development career, but it wasn't without struggles:

  • I dealt with burnout in July and took two weeks completely off code
  • I abandoned at least 5 projects because I lost motivation
  • I made dumb bugs that broke production (learned to use feature flags)
  • I second-guessed myself constantly
  • I compared myself to people with 10 years more experience

But I also:

  • Shipped real work that people use
  • Got better at my craft
  • Made meaningful connections
  • Built things I'm proud of
  • Learned a ton

Development isn't a straight line up. It's messy, frustrating, and sometimes demoralizing. But it's also creative, intellectually stimulating, and deeply satisfying when things finally work.

What's Your Year Look Like?

If you're a developer, what did 2025 teach you? What are you carrying into 2026?

I'd love to hear about your wins, your failures, and the lessons you learned. We're all figuring this out together.


Here's to 2026 - may your bugs be obvious, your deploys be smooth, and your coffee be strong.

Now back to building things.