Skip to content
C Codeloom
Git

Git Branching: A Beginner's Guide

Learn what a Git branch really is, how to create and switch branches, how to merge work back into main, and how to resolve a simple merge conflict.

·9 min read · By Yash Kesharwani
Beginner 11 min read

What you'll learn

  • What a branch actually is in Git (it is simpler than you think)
  • How to create, switch, list, rename, and delete branches
  • The difference between fast-forward and three-way merges
  • How to resolve a basic merge conflict step by step
  • A safe everyday workflow built around short-lived feature branches

Prerequisites

A branch in Git is the feature that turns it from a file-history tool into a serious development environment. Branches let you work on something risky — a new feature, a refactor, an experiment — without disturbing the version of the code that already works. They are cheap, instantaneous to create, and easy to throw away.

If you only learn one Git concept beyond add and commit, learn branches well.

What a branch actually is

Beginners often imagine a branch as a copy of the project, like a separate folder. It is not. A branch in Git is a single text file containing the hash of a commit. That is the entire implementation.

If you peek inside .git/refs/heads/main, you will find a 40-character hash and nothing else. The branch named main is just a movable pointer at that commit. When you make a new commit while on main, Git updates that one file to point at the new commit. The old commit is still there — it just no longer happens to be where main is.

This is why creating a branch in Git is essentially free. There is no copying. There is just a new tiny file pointing at the same commit you were already on:

git branch feature-login

You have not “branched off” anything yet. You have created a second pointer to the current commit. Branching only becomes interesting once one of the pointers moves while the other stays put.

Listing branches

git branch
  feature-login
* main

The asterisk marks the current branch — the one HEAD points at. In the example above, the working tree is still on main even though feature-login exists.

For more detail including the latest commit on each branch:

git branch -v

Switching branches

The modern command is git switch, introduced in Git 2.23 specifically to make branching less confusing. Use it.

git switch feature-login

HEAD now points at feature-login. Any commit you make from here updates feature-login, leaving main untouched. That is the whole point.

The older git checkout <branch> does the same thing and you will see it everywhere on the internet. Prefer git switch in new material; recognise checkout when you read it.

To create a new branch and switch to it in one step:

git switch -c feature-search

The -c is short for “create.”

A worked example

Let’s run through a realistic short workflow. Start in a clean repository on main with a few commits.

git log --oneline
c3d4e5f (HEAD -> main) Add README
b2c3d4e Initial commit

Create a branch for a new feature and switch to it:

git switch -c add-greeting

Create a file, stage it, commit it:

echo "console.log('Hello!');" > greeting.js
git add greeting.js
git commit -m "Add greeting script"

Check the log:

git log --oneline --all
d4e5f6a (HEAD -> add-greeting) Add greeting script
c3d4e5f (main) Add README
b2c3d4e Initial commit

add-greeting is now one commit ahead of main. Switch back to main:

git switch main

The file greeting.js disappears from your folder. Git has restored the working tree to look exactly like it did at the last commit on main. This is not destruction — the file still exists inside the repository, on the add-greeting branch. Switch back and it returns. Switching branches is how you move between parallel versions of the project on disk.

Merging — fast-forward

To incorporate the work from add-greeting into main, switch to main first, then merge in the other branch:

git switch main
git merge add-greeting
Updating c3d4e5f..d4e5f6a
Fast-forward
 greeting.js | 1 +
 1 file changed, 1 insertion(+)

Notice the phrase “Fast-forward.” Because main had not moved since add-greeting was created, Git did not need to combine two histories. It simply moved the main pointer forward to the same commit add-greeting is on. No new commit, no merge magic — just a pointer update.

After a fast-forward merge, both branches point at the same commit. You can safely delete the feature branch:

git branch -d add-greeting

The -d will refuse if the branch contains commits that are not on any other branch — a small but real safety net.

Merging — three-way

Fast-forward merges are clean but only happen when no one has touched main in the meantime. Real teams change main constantly. Suppose this happens:

            E---F  (feature)
           /
A---B---C---D  (main)

While you were working on feature (commits E and F), someone else added commit D to main. The two branches have diverged. To merge feature into main, Git cannot just move a pointer — it has to combine the changes from both sides into a new merge commit:

            E---F
           /     \
A---B---C---D-----M  (main)

M is the merge commit. It has two parentsD and F — which is what makes it a merge commit. Git asks you for a message (the default is fine for most cases). The resulting history is honest: both lines of work are preserved, and M records where they came back together.

This is called a three-way merge because Git uses three points to compute the result: the tip of each branch and their common ancestor (C in the diagram above).

Merge conflicts

If both branches modified the same lines of the same file, Git cannot decide which version to keep. You get a merge conflict:

git merge feature
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
Automatic merge failed; fix conflicts and then commit the result.

Open the conflicting file. You will see markers Git has inserted to show both versions:

<<<<<<< HEAD
const greeting = "Hello from main";
=======
const greeting = "Hi from feature";
>>>>>>> feature

Edit the file to whatever you want the final content to be — picking one side, the other, or something new — and remove all three markers (<<<<<<<, =======, >>>>>>>). Then:

git add app.js
git commit

Git pre-fills the merge commit message for you. Save and quit your editor to finish.

git status during a conflict is unusually helpful — it lists which files conflicted and what to do next. Read it carefully.

If a merge goes wrong and you want to bail out entirely, you can:

git merge --abort

This puts the repository back exactly the way it was before you ran git merge.

Try it yourself. In a practice repository: create a file notes.txt with one line and commit it on main. Create a branch b1, change the line, commit. Switch back to main, change the same line differently, commit. Switch to main if not already there and run git merge b1. Resolve the conflict by keeping a combination of both lines. Commit the merge.

Renaming and deleting branches

Renaming the current branch:

git branch -m new-name

Deleting a branch that has been merged:

git branch -d old-name

Deleting a branch with unmerged commits (force):

git branch -D old-name

Use the capital -D rarely and only when you genuinely intend to throw away work. It is one of the few Git commands that can lose commits without a warning.

A practical everyday workflow

The pattern most teams converge on, even small ones:

  1. main always contains working code.
  2. For every change — feature, bug fix, experiment — create a short-lived branch.
  3. Commit on the branch as much as you like; messy commits are fine here.
  4. When the change is ready and tested, merge it into main.
  5. Delete the branch.
# Start work
git switch main
git switch -c fix-login-error

# Make changes, commit
git add .
git commit -m "Fix login error on empty password"

# Merge back
git switch main
git merge fix-login-error
git branch -d fix-login-error

Branches in Git are so cheap that you should err on the side of more branches, not fewer. Many experienced developers create a branch for a change before they know if the change will be more than a few lines.

Try it yourself. In a practice repository with two existing commits on main, create a branch called experiment, make two commits on it, then return to main and confirm main is unchanged. Run git log --oneline --all --graph and read the picture Git draws of the two branches.

A useful viewing trick

The flags --all and --graph on git log are worth memorising:

git log --oneline --all --graph

This draws an ASCII picture of every branch’s history. It is the fastest way to understand the state of a repository at a glance, and it makes the rest of Git much more intuitive.

Recap

You now know:

  • A branch is just a pointer to a commit — creating one is essentially free.
  • git switch moves HEAD to a different branch and updates the working tree.
  • A fast-forward merge is a pointer move; a three-way merge creates a merge commit with two parents.
  • A merge conflict happens when the same lines were changed on both branches; you resolve it by editing, adding, and committing.
  • The everyday workflow is short-lived feature branches merged back into main.

Next steps

The next post tackles the question every Git learner eventually asks: should I merge or rebase? You will see what each one does, how to read the resulting history, and which to use in different situations.

→ Next: Git Merge vs Rebase — When to Use Each

Questions or feedback? Email codeloomdevv@gmail.com.