Back to Blog
·4 min read

Why I build internal tools before reaching for third-party solutions

Building your own tools first teaches you the problem domain and often leads to simpler, more maintainable solutions.

AI Dev
tooling
architecture
productivity
development

Why I build internal tools before reaching for third-party solutions

I've stopped reaching for third-party libraries as my first solution. When I need functionality that does not exist in my current stack, I build it myself first. This approach has saved me countless hours of debugging, reduced my bundle sizes, and taught me more about software engineering than any tutorial ever could.

The shift happened after I spent two days trying to make a popular form validation library work with our specific use case. The library had 200+ GitHub issues, required three peer dependencies, and still could not handle our nested object validation properly. I ripped it out and wrote 50 lines of TypeScript that did exactly what I needed.

The Hidden Costs of Third-Party Dependencies

Every dependency you add to your project is a bet. You're betting that the maintainer will keep it updated, that it won't introduce breaking changes at the worst possible moment, and that it actually solves your problem better than you could solve it yourself.

Bundle size creep -- That "lightweight" utility library might pull in 12 other packages you do not need. I've seen projects where 70% of the bundle size comes from dependencies that could be replaced with 20 lines of custom code.

Version hell -- Your authentication library needs React 18, but your date picker only supports React 17. Now you're stuck in dependency resolution purgatory.

Black box debugging -- When something breaks in a third-party library, you're digging through someone else's code to understand what went wrong. Good luck if it's minified or uses patterns you're not familiar with.

My "Build First" Process

When I encounter a problem that might need a new dependency, I follow this process:

  1. Understand the scope -- Write down exactly what I need the tool to do
  2. Time box a solution -- Give myself 2-4 hours to build a basic version
  3. Ship the MVP -- Use my solution in production to understand edge cases
  4. Evaluate alternatives -- Only then do I look at existing libraries

This approach has led to some surprising discoveries. That complex state management library? I replaced it with a 30-line hook that handles our specific use case perfectly:

function useFormState<T>(initialState: T) {
  const [state, setState] = useState<T>(initialState)
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})
 
  const updateField = (field: keyof T, value: T[keyof T]) => {
    setState(prev => ({ ...prev, [field]: value }))
    if (errors[field]) {
      setErrors(prev => ({ ...prev, [field]: undefined }))
    }
  }
 
  const validate = (rules: Partial<Record<keyof T, (value: T[keyof T]) => string | undefined>>) => {
    const newErrors: Partial<Record<keyof T, string>> = {}
    let hasErrors = false
 
    Object.entries(rules).forEach(([field, rule]) => {
      const error = rule(state[field as keyof T])
      if (error) {
        newErrors[field as keyof T] = error
        hasErrors = true
      }
    })
 
    setErrors(newErrors)
    return !hasErrors
  }
 
  return { state, errors, updateField, validate }
}

This hook handles 90% of our form needs without any external dependencies. It's tested, it's fast, and I understand every line of it.

When to Actually Use Third-Party Solutions

I'm not anti-dependency. Some problems are genuinely complex and well-solved by existing tools. I'll reach for third-party solutions when:

The domain is highly specialized -- Cryptography, date/time handling, or complex algorithms where expertise matters more than control.

The maintenance burden is too high -- Database drivers, payment processing, or security-critical code where staying updated is essential.

The solution is industry standard -- React, Express, or other foundational libraries where the ecosystem benefits outweigh the dependency costs.

My time box failed -- If I cannot build something reasonable in my allocated time, it's probably more complex than I initially thought.

The Learning Dividend

Building your own tools teaches you things you would never learn by importing someone else's solution. I understand HTTP caching better because I built my own request wrapper. I know more about parsing because I wrote my own markdown processor for this blog.

Each internal tool becomes a building block for future projects. That form hook I wrote? I've used variations of it in six different applications. It's evolved to handle new edge cases and requirements in ways a generic library never could.

The confidence that comes from understanding your tools is invaluable. When something breaks at 2 AM, I do not need to read documentation or wait for GitHub issues to be resolved. I can fix it immediately because I wrote it.

Start with the simplest thing that could possibly work. You might be surprised how often that simple thing is all you actually need.