Contributing to Open Source โ From First-PR Anxiety to Merged Code
How I got past the fear of my first pull request, found projects worth contributing to, and learned to read unfamiliar codebases without drowning.

My first open source pull request took me three weeks. Not because the change was complex โ I fixed a typo in documentation. Three words. The actual edit took thirty seconds. The other twenty days and twenty-three hours and fifty-nine minutes and thirty seconds were spent convincing myself the change was worth submitting, second-guessing the PR description, worrying that I'd somehow break the project's formatting, and drafting a commit message that I rewrote eleven times.
Submitted it. Got a one-line response: "Thanks, merged!" No fanfare. No judgment. No senior engineer explaining that my three-word fix was trivially obvious and I was wasting their time. Just a merge and a thank-you.
That tiny PR taught me something important: the barrier to contributing to open source isn't skill. It's anxiety, seems like. The code is the easy part. The human part โ putting your work in front of strangers for review โ is what stops most people. And the only way through it is doing it badly the first time and discovering that nobody cares about your imperfections nearly as much as you do.
Finding the Right Project
Not every open source project is a good place to start. Some have thousands of contributors, move fast, and expect context you don't have. Some are maintained by one person who hasn't merged a PR in six months. Finding the sweet spot matters.
What Makes a Project Contributor-Friendly
Active maintenance. Check the pulse: when was the last commit? When was the last PR merged? When was the last issue commented on? A project that hasn't seen activity in three months probably won't review your PR anytime soon. Look for projects where maintainers responded to issues within the last few weeks.
Contributing guide. Look for a CONTRIBUTING.md file. Projects that have one are explicitly inviting contributions. The guide tells you how to set up the development environment, what coding standards to follow, how to run tests, and how to format your PR. This documentation is a signal that maintainers value external contributions enough to write onboarding instructions.
Good first issue labels. GitHub's good first issue label exists specifically to flag tasks suitable for newcomers. These issues are typically well-scoped, have clear acceptance criteria, and don't require deep architectural knowledge. Browse them on GitHub's explore page: github.com/topics/good-first-issue.
Code of conduct. Its presence signals that the maintainers care about community health. Its absence doesn't mean the community is hostile, but its presence is a reassuring signal for a first-time contributor.
Where to Look
GitHub Topics and Explore are starting points. Filter by language, stars, and recent activity. But the projects I've found most welcoming came from tools I already use.
Using a CLI tool and the help text has a typo? That's a contribution. Using a library and the README's example code doesn't work with the latest version? That's a contribution. Hit a bug, found the cause, and the fix is straightforward? That's a contribution.
Contributing to something you use gives you context that cold-browsing GitHub doesn't. You already know what the project does, you've seen it in action, and you have opinions about what could be better. That context makes reading the codebase much less intimidating.
# Find repos in your ecosystem that welcome contributions
gh search repos --topic=good-first-issue --language=typescript --sort=updated --limit=20
A few ecosystems that are consistently welcoming to new contributors based on my experience: the Astro web framework, the Deno runtime, MDN Web Docs, freeCodeCamp, and most CNCF projects (like Kubernetes documentation โ you don't need to understand all of Kubernetes to fix a docs issue).
Reading an Unfamiliar Codebase
This is the skill that separates successful contributors from people who clone a repo, stare at the file tree for ten minutes, and close the tab. Reading unfamiliar code is a learnable skill, and I was bad at it for a long time.
Start With the Entry Point
Every application has an entry point. For a Node.js project, check package.json:
{
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest"
}
}
src/index.js is where execution begins. Start there. Read it top to bottom. Note which modules it imports. Follow the imports one level deep. You're building a mental map of how the pieces connect, not trying to understand every function.
For Rust projects, start at src/main.rs or src/lib.rs. For Go, find func main() in the cmd/ directory. For Python, look at __main__.py or the entry point defined in setup.py/pyproject.toml.
Read Tests Before Implementation
Tests tell you what the code is supposed to do. They're executable documentation. Before diving into a complex function's implementation, find its test file. The test cases show expected inputs, expected outputs, and edge cases the author considered.
// Reading this test tells you exactly what parseConfig does
// without reading the implementation
describe('parseConfig', () => {
it('returns defaults when no config file exists', () => {
expect(parseConfig('/nonexistent')).toEqual({ port: 3000, host: 'localhost' });
});
it('merges file config with defaults', () => {
expect(parseConfig('./fixtures/partial.json')).toEqual({
port: 8080,
host: 'localhost' // default preserved
});
});
it('throws on invalid JSON', () => {
expect(() => parseConfig('./fixtures/broken.json')).toThrow('Invalid config');
});
});
Three test cases and I understand the function's contract without reading a single line of the implementation. I use this approach constantly when exploring unfamiliar projects.
Use the Debugger, Not Just Reading
Clone the project, run it locally, and step through the code path related to the issue you're fixing. Reading code statically only gets you so far. Watching it execute โ seeing which branches are taken, what values variables hold, when functions are called โ fills in gaps that reading alone misses.
# Clone and set up
git clone https://github.com/some/project.git
cd project
npm install
npm test # Make sure everything passes before you change anything
That last point deserves emphasis: run the tests before you make any changes. If tests fail on a clean clone, you know it's a project issue, not something you broke. If tests pass, you have a baseline. After your changes, run them again. If something new fails, your change caused it.
Don't Try to Understand Everything
A project with 200 files and 50,000 lines of code โ you don't need to understand all of it to fix a bug or add a small feature. You need to understand the 3-5 files that are relevant to the issue. Trace the code path from input to output for the specific behavior you're modifying. Ignore everything else.
This was my biggest mistake early on. Tried to understand entire codebases before contributing. Felt like I needed complete knowledge to change three lines. You don't. Surgeons don't understand every cell in the body to perform an operation. They understand the relevant anatomy deeply and the surrounding context well enough. Same principle.
Making Your First Contribution
Start small. Seriously. The instinct is to pick something impressive โ a new feature, a performance optimization, a refactor. Resist that instinct. First contributions should be low-risk, well-defined, and quick to review.
Documentation and Typo Fixes
Not glamorous. Genuinely valuable. Documentation quality directly affects adoption. Maintainers appreciate documentation contributions because they often don't have time to write or update docs themselves. And for you, it's a low-stakes way to learn the contribution workflow โ forking, branching, committing, opening a PR โ without risking a code-breaking change.
Bug Fixes With Reproduction Steps
Found a bug? Even better if the issue already has reproduction steps. Fork the project, reproduce the bug locally, write a failing test, fix the code, verify the test passes. This is the gold standard contribution:
# Fork on GitHub, then:
git clone https://github.com/YOUR-USERNAME/project.git
cd project
git checkout -b fix/issue-123-null-check
npm install && npm test # Baseline
# Write a failing test for the bug
# Fix the bug
# Verify the test passes
git add src/parser.js test/parser.test.js
git commit -m "fix: handle null input in parser (#123)
Added null check in parseInput() that was causing TypeError
when called with undefined config. Added test case to prevent
regression."
git push origin fix/issue-123-null-check
Then open the PR on GitHub, referencing the issue.
PR Etiquette โ What Maintainers Actually Want
After contributing to a few dozen projects and maintaining a couple of small ones myself, here's what I've observed about PRs that get merged versus PRs that get ignored.
Keep It Focused
One PR, one change. Don't fix a bug and also refactor the surrounding code and also update the README. The maintainer now has to review three unrelated changes, any of which might be controversial. The bug fix might be great but the refactor might be unwanted. By combining them, the whole PR gets stuck.
If you notice other improvements while fixing a bug, make separate PRs. The bug fix gets merged quickly. The refactor gets its own discussion.
Explain Why, Not Just What
The PR description matters. "Fixed the bug" tells the maintainer nothing. What bug? How did you find it? What was the root cause? How does your fix address it?
## What
Fixed `TypeError: Cannot read property 'name' of undefined` when
`parseConfig()` receives null input.
## Why
Issue #123 โ users passing environment variables that evaluate to
empty strings trigger this error because the config loader doesn't
distinguish between "no config" and "empty config."
## How
Added a null/undefined check at the entry point of `parseConfig()`.
When input is falsy, returns default config instead of attempting
to access properties on undefined. Added a test case covering this
scenario.
## Testing
- Added test: `parseConfig(null)` returns defaults
- Added test: `parseConfig(undefined)` returns defaults
- All existing tests pass
This takes two minutes to write and saves the maintainer ten minutes of figuring out your intent. That ratio matters when a maintainer has 30 PRs to review.
Respond to Feedback Graciously
Your PR will probably get change requests. This isn't criticism โ it's collaboration. The maintainer knows the codebase better and might see implications of your change that you missed. They might ask you to rename a variable, add a test case, or restructure the code.
Respond promptly. Make the requested changes. If you disagree with a suggestion, explain your reasoning respectfully. "I chose this approach because X, but I'm open to Y if you think it fits better" is productive. "I think my way is fine" without explanation is not.
I've seen perfectly good contributions abandoned because the author got defensive about review feedback. Don't be that person. The review process is where you learn the most. My code improved more from PR reviews on open source projects than from any course or tutorial.
Don't Take Silence Personally
Submitted a PR and heard nothing for two weeks? Maintainers are often volunteers with full-time jobs. A polite follow-up comment after a week or two is fine: "Hi, just checking if this PR is on your radar. Happy to make any changes needed." Don't ping daily. Don't open new issues asking why your PR hasn't been reviewed.
Some PRs never get merged. Maybe the project's direction changed. Maybe a maintainer implemented the same thing differently. Maybe the project went dormant. Doesn't mean your contribution was bad. It means open source is messy and human.
Beyond Bug Fixes โ Growing Your Contributions
Once you're comfortable with the contribution workflow, you can take on larger tasks.
Improve test coverage. Many projects have gaps in their test suites. Adding tests is valuable, helps you understand the code deeply, and is lower risk than code changes because tests don't affect the running application.
Triage issues. Reproduce bug reports. Add labels. Close duplicates. Ask for more information on vague issues. This requires no code changes and helps maintainers enormously. Regular triage contributors often become trusted community members.
Review other people's PRs. You don't have to be a maintainer to leave helpful review comments. Spot a bug in someone's PR? Point it out. See an approach that could be simplified? Suggest it. Code review is a contribution, and doing it well is a skill that benefits your career beyond open source.
Propose features. After contributing a few PRs, you understand the project's direction and conventions. Propose a feature by opening an issue first โ describe the problem, your proposed solution, and alternatives you considered. Get maintainer buy-in before writing code. Nothing worse than spending a weekend on a feature PR that gets rejected because it conflicts with the project's roadmap.
What Open Source Did for My Career
Being honest: early in my career, open source contributions mattered more on my resume than my professional work. My job title said "junior developer." My GitHub profile showed contributions to projects that hiring managers recognized. That contrast opened doors.
Beyond the resume signal, the skills transfer directly. Reading unfamiliar codebases โ I do that professionally every time I join a new team. Writing clear PR descriptions โ same skill as communicating technical decisions at work. Responding to code review โ identical process in any professional engineering team. Working asynchronously with people across time zones โ increasingly how all software gets built.
The Git workflow for open source is the same workflow used by professional engineering teams. If you can fork, branch, commit, push, open a PR, respond to reviews, and merge โ you can operate in any professional development environment. Those mechanics are worth learning through open source contributions where the stakes are low and the feedback is free.
Common Fears (and Why They're Overblown)
"My code isn't good enough." Neither was mine. Neither was anyone's when they started. The review process exists specifically to catch issues. Submit imperfect code. Get feedback. Improve it. That's the system working as intended.
"I'll break something." You can't break a project with a PR. PRs are proposals. The maintainer reviews them before merging. If your change would break things, they'll catch it (or their CI will). The worst that happens is your PR gets closed with an explanation.
"I don't understand the codebase well enough." You don't need to. Understand the part you're changing. Ask questions in the issue thread if something is unclear. Maintainers prefer a contributor who asks questions over one who guesses wrong.
"Experienced contributors will judge me." Most experienced open source contributors remember being nervous about their first PR. The community is generally welcoming because everyone went through the same thing. The rare person who's dismissive or rude is the exception, not the norm, and most projects' codes of conduct handle that.
"I don't have time." A documentation fix takes 15 minutes. A small bug fix takes an hour or two. You don't need to dedicate weekends. One small contribution per month is enough to build momentum and learn the workflow.
Getting Started Today
Pick a project you use. Check the issues. Find one labeled good first issue or help wanted. Read the contributing guide. Fork the repo. Make the change. Open the PR. That's it.
The first one is the hardest. Not technically โ emotionally. Every subsequent contribution gets easier because you've proven to yourself that the sky doesn't fall when you submit imperfect work to strangers on the internet.
Open source is how most of the software we depend on gets built. The web frameworks, the databases, the operating systems, the tools we use every day โ built by people who started exactly where you are. Nervous. Uncertain. Submitting a small fix and hoping someone would merge it.
They did. And here we are.
Keep Reading
- Post-Mortem: A Disastrous Git Merge and the Resulting Workflow โ The fork-branch-PR workflow for open source is the same one that prevents disasters on professional teams.
- Clean Code Without the Dogma โ What Actually Matters in Practice โ Writing contributions that get merged starts with writing code that reviewers can read and trust.
Written by
Anurag Sinha
Full-stack developer specializing in React, Next.js, cloud infrastructure, and AI. Writing about web development, DevOps, and the tools I actually use in production.
Stay Updated
New articles and tutorials sent to your inbox. No spam, no fluff, unsubscribe whenever.
I send one email per week, max. Usually less.
Comments
Loading comments...
Related Articles

The 5 Rules of Git Branching for High-Velocity Teams
Practical constraints for code integration that actually work. Less about naming conventions, more about not breaking production.

Post-Mortem: A Disastrous Git Merge and the Resulting Workflow
A detailed breakdown of a force-push incident that deleted two days of work, and the strict git branching policies established in the aftermath.

Design Patterns in JavaScript โ The Ones That Actually Show Up in Real Code
Forget the Gang of Four textbook. These are the patterns I see in production JavaScript and TypeScript codebases every week โ observer, factory, strategy, and the ones nobody names but everyone uses.