You Don’t Need That Library
JS Edition
January 29, 2026
Installed a library last week to debounce a function. Then realized I could’ve written it in five lines.
Felt a little dumb.
The Thing We All Do
Problem comes up. Google the problem. Find a library. npm install. Import it. Done.
I’ve done this hundreds of times. It’s fast. It works. Why reinvent the wheel?
Then your bundle size hits 500KB and you’re wondering where it all came from.
When I Started Questioning This
Built a small project. Just a form with some validation. Added a date picker library because writing one seemed hard. Added a library for formatting phone numbers. Added lodash for a few array operations. Added moment.js for date handling.
Bundle size: 400KB. For a form.
Opened the bundle analyzer. Half my JavaScript was libraries doing things I barely used.
The date picker? Using 5% of its features. Phone formatting? Could’ve been a regex. Lodash? Using three functions. Moment.js? Just formatting one date.
That’s when it clicked. I wasn’t choosing libraries because they solved hard problems. I was choosing them because they were there.
What Modern JavaScript Can Actually Do
JavaScript added a lot of stuff in the last few years. Things we used to need libraries for? Built in now.
Used to need jQuery for:
$('.class').addClass('active');
Now:
document.querySelectorAll('.class').forEach(el => {\n el.classList.add('active');\n});
Little more verbose. But not enough to justify a library.
Used to need lodash for:
_.debounce(fn, 300);
Now:
let timeout;\nfunction debounce(fn, delay) {\n return (...args) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => fn(...args), delay);\n };\n}
Five lines. Not hard.
Used to need moment.js for:
moment(date).format('YYYY-MM-DD');
Now:
new Date(date).toISOString().split('T')[0];
Or if you need more control:
new Intl.DateTimeFormat('en-CA').format(date);
Moment.js is 70KB minified. That’s a lot for date formatting.
The Bundle Size Thing
Every library you add is JavaScript your users download. On slow connections, that matters.
Looked at my phone’s network tab. Loading a site on 3G. 500KB of JavaScript took 15 seconds to download. Then the browser had to parse it. Then execute it. Total time to interactive: 25 seconds.
Same site with 100KB of JavaScript? 8 seconds total.
Users don’t care about your tech stack. They care that the site loads fast.
The Things I Stopped Installing
Lodash
Used to install it on every project. Now I check if I actually need it. Usually I don’t.
Array methods like map, filter, reduce, find, some, every - all built in. Object methods like Object.keys, Object.values, Object.entries - built in.
The deep clone function? Okay, that’s still useful. But you can install just that one function: npm install lodash.clonedeep. 6KB instead of 70KB.
Moment.js
Heavy. Mutable APIs are confusing. Modern alternative is date-fns (modular, tree-shakeable) or just use native Date with Intl.
Most of the time I just need basic formatting. Don’t need a 70KB library for that.
Axios
Fetch API is built in now. Works in all modern browsers.
// Used to do this\naxios.get('/api/data').then(response => {\n console.log(response.data);\n});\n\n// Now just\nfetch('/api/data')\n .then(res => res.json())\n .then(data => console.log(data));
Axios has nice features (interceptors, automatic JSON parsing, better errors). But for simple API calls? Fetch is fine.
jQuery
Still see it in projects. Usually just for DOM manipulation and AJAX.
Modern JavaScript can do all of that. Unless you’re supporting IE11 (please don’t), you don’t need jQuery anymore.
UUID libraries
Used to install a library to generate UUIDs.
// Now\ncrypto.randomUUID();
Built in. Works everywhere modern.
Classnames
Popular library for conditional CSS classes in React.
// Library\nclassName={classnames('btn', { active: isActive })}\n\n// Vanilla\nclassName={`btn ${isActive ? 'active' : ''}`}\n```\n\nIs the library version nicer? Sure. Worth the dependency? Probably not.\n\n## When I Still Install Libraries\n\nNot anti-library. Just more careful about it.\n\n**Complex UI components** \nDate pickers, rich text editors, data tables with sorting/filtering. These are hard to build well. Use a library.\n\nI'm not writing a date picker from scratch. Too many edge cases. Accessibility concerns. Browser quirks. Someone else solved this already.\n\n**Hard algorithms** \nImage manipulation, data visualization, complex math. Don't reinvent this.\n\nD3 for charts? Worth it. Canvas manipulation library? Probably worth it. These solve genuinely hard problems.\n\n**Framework ecosystems** \nReact Router, Redux, Vue Router. These integrate deeply with their frameworks. Use them.\n\nCould you build your own state management? Sure. Should you? Probably not.\n\n**Established, maintained libraries** \nReact, Vue, Angular. These aren't going away. They're solving real problems with years of refinement.\n\nNot the same as installing a utility library that does what three lines of JavaScript could do.\n\n## The Mental Model I Use Now\n\nBefore installing a library, I ask:\n\n**1. Can I write this in under 20 lines?** \nIf yes, write it. Five years from now, those 20 lines still work. The library might be abandoned.\n\n**2. Is this a solved problem with many edge cases?** \nDate pickers, validators, parsers - lots of edge cases. Use a library.\n\n**3. Is this a core part of my app?** \nIf it's central to what you're building, maybe write it yourself. More control. Less dependency risk.\n\n**4. What's the bundle size?** \nCheck bundlephobia.com. If it's 50KB for something simple, reconsider.\n\n**5. Is it actively maintained?** \nLast commit two years ago? Probably don't add it to your project.\n\n## The Dependencies Problem\n\nEvery library has dependencies. Those have dependencies. Suddenly you've got 300 packages for one feature.\n```\nnpm install some-library\n\nadded 47 packages, and audited 437 packages in 3s
Wait, I installed one library. Why are there 47 new packages?
This is dependency hell. Each package brings its own dependencies. Your node_modules folder grows. Your bundle grows. Your attack surface for security vulnerabilities grows.
Sometimes it’s worth it. Often it’s not.
Real Example: My Form Library Moment
Needed form validation. Found a popular library. 60KB. Lots of features.
Actually needed: Check if email is valid. Check if field is empty. Show error messages.
Wrote it myself:
function validateEmail(email) {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n}\n\nfunction validateRequired(value) {\n return value.trim().length > 0;\n}\n\nfunction showError(field, message) {\n const error = field.nextElementSibling;\n error.textContent = message;\n error.style.display = 'block';\n}
About 30 lines total for everything I needed. Saved 60KB.
Is this better than the library? Depends. For my use case, yes. For complex validation rules across many forms, maybe not.
When Simple Code Is Better
Library code tries to handle every case. That’s good. But also heavy.
Your code only needs to handle your cases. That’s lighter.
// Library approach - handles everything\n_.get(obj, 'user.profile.name', 'Default');\n\n// Your approach - handles what you need\nobj?.user?.profile?.name || 'Default';
Optional chaining and nullish coalescing are built in now. Don’t need lodash for this anymore.
The Maintenance Angle
Libraries need updates. Security patches. Breaking changes between major versions.
I’ve spent hours upgrading libraries because a security vulnerability was found. Or because a new major version broke my code.
Your own 20 lines of code? Never breaks. Never needs updating. Never has security vulnerabilities (okay, unless you wrote bad code, but that’s on you).
Less code to maintain is valuable.
The Learning Thing
Writing code yourself teaches you how things work.
I understood debouncing way better after implementing it once. Now I can tweak it for specific needs. Can’t do that if it’s a black box in a library.
Not saying rewrite everything for learning. But some things are worth implementing once to understand them.
What I Actually Do Now
Check if modern JavaScript can do it. If yes, use that.
If no, check if I can write it simply. If yes, write it.
If no, check bundle size and maintenance status. If reasonable, install the library.
Basically: default to vanilla, add libraries when they’re actually worth it.
The Exceptions
Some libraries are just good. Well-maintained, small, solving real problems.
date-fns - Modular date library, tree-shakeable
zod - Type-safe validation, excellent DX
clsx - Tiny classname utility (300 bytes)
React/Vue/Svelte - Frameworks that are worth their weight
These are solving real problems efficiently. Use them.
But do you need a library to capitalize a string? No.
// Don't need a library for this\nconst capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
The Honest Part
I still install libraries I don’t strictly need sometimes. Convenience matters. Developer experience matters. Time matters.
If I’m prototyping, I’m not writing everything from scratch. I’ll grab whatever gets me moving.
But for production code that matters? I think twice. Check the bundle. Consider the maintenance burden. Ask if I really need it.
More often than I used to, the answer is no.
What Changed My Mind
Saw a site load slowly on my phone. Checked the network tab. 800KB of JavaScript. Most of it libraries doing simple things.
Realized my bundle size affects real people on real connections. Not everyone has fast internet and new phones.
Started caring about bundle size. Started questioning every install.
Not dogmatic about it. Just more thoughtful.
Your users download every byte you ship. Make it count.
Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.