GitHub Pull Requests and Code Review Explained for Beginners
Every professional software team on the planet shares code. Not by emailing zip files or copying folders — they use a structured process where every single change gets proposed, discussed, and approved before it touches the production codebase. GitHub Pull Requests are the mechanism that makes this possible, and they're the beating heart of collaborative software development at companies like Google, Netflix, and Spotify.
Without a review process, one developer's typo can break the app for every user at 2am on a Friday. One misunderstood requirement can send a team down the wrong path for a week. Pull Requests solve this by creating a formal checkpoint: before any code merges into the main branch, at least one other human looks at it. That human catches bugs, asks clarifying questions, and ensures the change actually fits the bigger picture.
By the end of this article you'll know exactly what a Pull Request is, how to open one from scratch on GitHub, how to leave a code review that your teammates will actually appreciate, and how to avoid the three mistakes that make junior developers cringe in retrospect. You'll also have real terminal commands you can run right now, today.
What Is a Branch and Why You Need One Before a Pull Request
Before you can open a Pull Request, you need to understand branches — because a PR is really just a request to merge one branch into another.
Think of the main branch (often called main or master) as the official published version of your project — the printed textbook. A branch is your personal draft copy where you can experiment, write new features, or fix bugs without touching the published version. Once your work is ready and reviewed, you merge your branch back into main.
Here's the workflow in plain English: you copy the current state of main into a new branch, make your changes there, push that branch to GitHub, and then open a Pull Request to say 'hey team, I've made these changes on my branch — can someone review them before we include them in main?'
The branch name should describe the work you're doing. fix-login-button-alignment is a great branch name. my-branch or test123 will confuse everyone including future you. One branch per feature or bug fix is the golden rule — keep your PRs focused and small.
# Make sure you start from an up-to-date main branch # This avoids merge conflicts caused by working on stale code git checkout main git pull origin main # Create a new branch with a descriptive name that explains the work # 'checkout -b' creates the branch AND switches to it in one step git checkout -b feature/add-user-profile-page # Confirm you are now on the new branch — the asterisk (*) marks the active branch git branch # --- Make your changes to the code here --- # For this example, we'll create a new file to simulate real work echo "<h1>User Profile Page</h1>" > user-profile.html # Stage the new file — 'add' tells Git to track this file in the next commit git add user-profile.html # Commit with a message that explains WHAT changed and WHY # Present tense, imperative mood is the professional standard: 'Add' not 'Added' git commit -m "Add initial user profile page HTML structure" # Push this branch up to GitHub so others can see it # '-u origin' links your local branch to the remote GitHub branch (only needed first time) git push -u origin feature/add-user-profile-page
Your branch is up to date with 'origin/main'.
Switched to a new branch 'feature/add-user-profile-page'
* feature/add-user-profile-page
main
[feature/add-user-profile-page a3f92c1] Add initial user profile page HTML structure
1 file changed, 1 insertion(+)
create mode 100644 user-profile.html
Branch 'feature/add-user-profile-page' set up to track remote branch 'feature/add-user-profile-page' from 'origin'.
To https://github.com/your-username/your-project.git
* [new branch] feature/add-user-profile-page -> feature/add-user-profile-page
How to Open a Pull Request on GitHub Step by Step
Once your branch is pushed to GitHub, opening the Pull Request takes about two minutes — but writing a good PR description takes a bit more thought, and that effort pays off every single time.
After you push a branch, GitHub usually shows a yellow banner on the repository page saying 'Your recently pushed branch... Compare & pull request'. Click that button. If the banner is gone, click the 'Pull requests' tab, then 'New pull request', and select your branch from the dropdown.
The PR form has three key parts: the title, the description, and the reviewers. The title should be a one-line summary of what the change does. The description is where you explain the context — why does this change exist? What does it do? How can the reviewer test it? A template like 'What, Why, How to Test' makes this systematic.
Assigning reviewers is critical. GitHub lets you request specific people to review your code. In a team setting, at least one approval is typically required before merging. You can also add labels ('bug', 'enhancement'), link to a related issue, and mark a PR as a 'Draft' if it's not ready for review yet — that's a great way to share work-in-progress and get early feedback without it accidentally getting merged.
## What Does This PR Do? <!-- One or two sentences. What is the end result of merging this branch? --> Adds an initial HTML structure for the User Profile page, which will display the logged-in user's name, avatar, and recent activity feed. ## Why Is This Change Needed? <!-- Link to the issue or explain the business reason --> Addresses issue #47 — users currently have no dedicated profile page. This is the first step in the Q3 personalisation milestone. ## How to Test It <!-- Tell the reviewer exactly what steps to follow to verify the change works --> 1. Pull this branch locally: `git fetch && git checkout feature/add-user-profile-page` 2. Open `user-profile.html` in a browser 3. Confirm the heading 'User Profile Page' renders correctly 4. Confirm no existing pages are broken by checking `index.html` still loads ## Screenshots (if relevant) <!-- Drag and drop images here — essential for any UI changes --> [Before: no profile page exists] [After: /user-profile.html shows heading] ## Checklist - [x] I have tested this change locally - [x] I have added comments to complex code sections - [ ] I have updated the documentation (not needed for this change) - [x] No new console errors or warnings introduced
When pasted into the GitHub PR description box, GitHub renders it as:
A formatted description with bold headings, a numbered test list,
an image drop zone, and interactive checkboxes that teammates
can tick as they verify each item.
How to Do a Code Review That Actually Helps
Being asked to review code is a responsibility, not a chore. A good review catches real bugs, shares knowledge across the team, and makes the codebase better. A bad review is either rubber-stamping everything or leaving vague, discouraging comments.
On GitHub, click the 'Files changed' tab in a PR to see a side-by-side diff — the red lines are what was removed, the green lines are what was added. You can click the '+' icon that appears when you hover over any line to leave an inline comment on that specific line.
There are three things to look for when reviewing: correctness (does it actually work?), clarity (can I understand what this code does in 30 seconds?), and consistency (does it follow the patterns the rest of the codebase uses?).
When you leave a comment, be specific and constructive. Instead of 'this is wrong', say 'this function will throw a null reference error if the user has no profile photo set — what if we add a fallback here?' GitHub lets you prefix comments with Nitpick: for minor style preferences so the author knows what's critical versus optional.
When you're done reviewing, click 'Review changes' and choose one of three options: Comment (general feedback, no decision), Approve (looks good to merge), or Request Changes (specific issues that must be fixed first). Only approve when you'd genuinely be comfortable with this code shipping.
// This is the kind of code you might review in a PR // The inline comments below simulate what a good reviewer would flag // REVIEWER COMMENT on line 3: // 'getUserData' is too vague. What kind of data? Rename to 'fetchUserProfileById' // to make the intent clear without reading the implementation. function getUserData(id) { // REVIEWER COMMENT on line 7: // No input validation here. What happens if 'id' is null, undefined, // or a negative number? This will hit the API and potentially return // confusing errors. Suggest adding: if (!id || id <= 0) return null; const apiUrl = `https://api.example.com/users/${id}`; return fetch(apiUrl) .then(function(serverResponse) { // REVIEWER COMMENT on line 14: // We should check serverResponse.ok before calling .json() // If the API returns a 404 or 500, .json() will still run // and we'll get a misleading error, not a clear 'user not found'. return serverResponse.json(); }) .then(function(userData) { return userData; }) // REVIEWER APPROVE NOTE: // Good job adding a .catch() — error handling is often forgotten. // Consider logging the error to your monitoring service here too. .catch(function(networkError) { console.error('Failed to fetch user profile:', networkError); return null; }); } // IMPROVED VERSION after review feedback: function fetchUserProfileById(userId) { // Guard clause — fail fast with a clear reason if (!userId || userId <= 0) { console.warn('fetchUserProfileById called with invalid userId:', userId); return Promise.resolve(null); } const profileApiUrl = `https://api.example.com/users/${userId}`; return fetch(profileApiUrl) .then(function(serverResponse) { // Explicitly check for HTTP errors before parsing the body if (!serverResponse.ok) { throw new Error(`User profile API returned status: ${serverResponse.status}`); } return serverResponse.json(); }) .then(function(userProfileData) { return userProfileData; }) .catch(function(networkError) { console.error('Failed to fetch user profile:', networkError.message); return null; }); }
The value here is the inline review comments that demonstrate
what a senior developer looks for:
1. Naming clarity (getUserData -> fetchUserProfileById)
2. Input validation (guard clause for invalid userId)
3. HTTP error handling (checking serverResponse.ok)
4. Constructive tone: explains the WHY, not just 'this is wrong'
Merging a Pull Request and Keeping History Clean
Once a PR has the required approvals and all CI checks pass (tests, linting, etc.), it's ready to merge. GitHub gives you three merge strategies and picking the right one matters for keeping your Git history readable.
'Create a merge commit' preserves every commit from your branch and adds a merge commit on top. This is great for long-running features where you want to see the full development history.
'Squash and merge' takes all your commits and compresses them into a single commit on main. This is perfect for small features or bug fixes where your branch had messy 'WIP' or 'fix typo' commits that aren't worth preserving. The result is a clean, linear history.
'Rebase and merge' replays your branch commits directly on top of main without a merge commit, keeping history linear. It's the cleanest option but can cause confusion if your branch had public commits others were building on.
After merging, always delete the feature branch — GitHub shows a 'Delete branch' button right after the merge. Stale branches pile up fast and confuse the whole team. Locally, run git branch -d to clean up too.
If the PR has merge conflicts — meaning main changed in ways that clash with your branch — you'll need to resolve them before merging. GitHub can handle simple conflicts in the browser, but for complex ones, resolve them locally.
# --- After your PR is approved and merged on GitHub --- # Switch back to main and pull the freshly merged changes git checkout main git pull origin main # Confirm your feature branch changes are now part of main # The --oneline flag shows a compact one-line-per-commit view git log --oneline -5 # Delete the local feature branch — the work is done, the branch is no longer needed # '-d' (lowercase) is safe: it refuses to delete if the branch hasn't been merged yet git branch -d feature/add-user-profile-page # Confirm the branch is gone from your local machine git branch # --- If you need to resolve a merge conflict before merging --- # First, pull the latest main into your feature branch to surface the conflict locally git checkout feature/add-user-profile-page git fetch origin git merge origin/main # Git will tell you which files have conflicts, e.g.: # CONFLICT (content): Merge conflict in user-profile.html # Open that file — you'll see conflict markers like this: # <<<<<<< HEAD (your changes) # <h1>User Profile</h1> # ======= (what's in main) # <h1>User Details</h1> # >>>>>>> origin/main # Edit the file to keep the correct version, then: git add user-profile.html git commit -m "Resolve merge conflict: keep User Profile heading from feature branch" git push origin feature/add-user-profile-page # Now your PR on GitHub can be merged cleanly
Your branch is up to date with 'origin/main'.
Already up to date.
a3f92c1 Add initial user profile page HTML structure
7b2e4d9 Update navigation links in header
3c8f1a2 Fix broken image path on homepage
9d4c7e8 Add footer contact information
1e5b0f3 Initial project setup
Deleted branch feature/add-user-profile-page (was a3f92c1).
* main
| Aspect | Merge Commit | Squash and Merge | Rebase and Merge |
|---|---|---|---|
| History style | Branched (non-linear) | Linear — one clean commit | Linear — all commits replayed |
| Individual commits preserved? | Yes — every commit kept | No — squashed into one | Yes — but rebased onto main |
| Best for | Long-running feature branches | Small features, bug fixes | Clean commit history fans |
| Merge commit added? | Yes | Yes (squash commit) | No |
| Risk level for beginners | Low — safest option | Low — very popular choice | Medium — can cause confusion |
| When to avoid | When branch has 20 'wip' commits | When commit history matters for debugging | When others branched off your branch |
🎯 Key Takeaways
- A Pull Request is a formal request to merge one branch into another — it's the review checkpoint that stands between your code and production, not just a Git feature.
- A great PR description answers three questions: What changed, why it changed, and how to test it — skipping this is the single biggest thing that slows down code reviews.
- Approve means 'I'd be comfortable if this shipped right now' — never click Approve to be polite; a hollow approval that lets a bug through is worse than a delayed review.
- Squash and Merge keeps main's history readable by collapsing messy WIP commits into one clean entry — prefer it for small features and bug fixes unless individual commit history matters for debugging.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Opening a PR directly against main from a branch you never updated — Symptom: GitHub shows 15+ merge conflicts and the PR diff is enormous. Fix: Before pushing your branch, always run
git fetch origin && git merge origin/mainto incorporate the latest changes from main into your branch first. Resolve any conflicts locally, then push. Your PR will then only show the changes you actually made. - ✕Mistake 2: Writing a PR description that just says 'fixed stuff' — Symptom: Reviewers ask three clarifying questions before they can even start reviewing, slowing the whole process down. Fix: Always answer three questions in your PR description: What does this change? Why does it need changing? How can a reviewer verify it works? Paste the description template from the second section of this article and fill it in for every single PR, no matter how small.
- ✕Mistake 3: Responding defensively to review feedback — Symptom: The PR comment thread becomes a debate instead of a collaboration, and the reviewer either gives up or the author force-merges over objections. Fix: Treat every review comment as a question, not an attack. Reply with 'Good point — I've updated line 14 to handle the null case' or 'I chose this approach because X — does that change your concern?' If you disagree, explain your reasoning calmly. The goal is the best possible code, not being right.
Interview Questions on This Topic
- QWalk me through the complete lifecycle of a Pull Request — from creating a branch to the code being live on the main branch. What steps happen and who is responsible for each?
- QWhat is the difference between Squash and Merge, Merge Commit, and Rebase and Merge on GitHub? Which would you choose for a small bug fix and why?
- QYou open a PR and your reviewer leaves a comment saying your function has no error handling. You disagree because you think error handling belongs in the calling code, not the function itself. How do you handle this situation?
Frequently Asked Questions
What is the difference between a pull request and a merge request?
They're the same concept with different names. GitHub and Bitbucket call it a Pull Request (PR). GitLab calls it a Merge Request (MR). Both describe the same workflow: you propose merging one branch into another, get it reviewed, and then merge it once approved.
How many people need to approve a pull request before it can be merged?
That depends on the team's branch protection rules, which are configured in GitHub under Settings > Branches. Most professional teams require at least one approval, often two for critical repositories. As a beginner on a solo project, you can set it to zero — but getting into the habit of requesting reviews even from one peer dramatically improves code quality.
Can I keep committing to my branch after opening a pull request?
Yes, absolutely — and this is how it's meant to work. When you push new commits to your branch after the PR is open, GitHub automatically updates the PR with your new changes. Reviewers can see exactly what changed since their last review. This is how you address review feedback: make the fix locally, commit it, push, and the PR updates in real time.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.