# GitHub flow: small branches, fast merges

> The loop most product teams work in: branch from main, pull request, review and checks, merge, deploy. Why small, short-lived branches are safer, what "merged" and "deployed" mean, how feature flags let unfinished work ship hidden, and how to read a feature's progress from the pull request list.

- Course: [Git and GitHub for Product People](https://productovi.com/courses/git-for-product.md)
- Module: Team workflows
- Lesson 10/15, ~41 min
- Source: https://productovi.com/courses/git-for-product/github-flow

## Is it live yet?

**Alex Tutor:** Friday afternoon at CatChow, right after your product team's meeting. Which question did you hear most today?

**Sofia:** "Is it live yet?" Three times. Once about the checkout fix, twice about Emma's autumn sale banner.

**Alex Tutor:** Then let's put the whole path of a change on one picture. The team has a name for it: **GitHub flow**. You've already seen every step, just never all at once.

```text
Commit graph, oldest first: main over the last two weeks
1. commit e7c3b19 on main "Add summer promo banner"
2. branch content/delivery-copy starts from commit e7c3b19
3. commit e19c3b7 on content/delivery-copy "Rewrite delivery page copy"
4. commit f02a6d4 on main "Update FAQ answers"
5. branch fix/checkout-button starts from commit f02a6d4
6. commit 3a9d1c4 on fix/checkout-button "Reproduce bug in a test"
7. commit 8be2f70 on main "Add cat treats bundle"
8. branch feature/free-delivery-1000 starts from commit 8be2f70
9. commit 5c1d7e2 on feature/free-delivery-1000 "Set freeDeliveryFrom to 1000"
10. commit a8f40b9 on feature/free-delivery-1000 "Update delivery page text"
11. commit d36b9c5 on feature/free-delivery-1000 "Update cart hint text"
12. commit b7e4a05 on fix/checkout-button "Re-enable button after cart edits"
13. commit 6f2c8a1: feature/free-delivery-1000 squash-merged into main "Raise free-delivery threshold to 1000 UAH (#133)"
14. merge commit c90d4e7: main merged into content/delivery-copy "Merge branch 'main' into content/delivery-copy"
15. commit a7d93e4: content/delivery-copy squash-merged into main "Rewrite delivery page copy (#132)"
16. commit c2d7f90 on fix/checkout-button "Update checkout hint copy"
17. commit f7a2b61 on fix/checkout-button "Restore checkout button colour"
18. merge commit 9e3b5d2: main merged into fix/checkout-button "Merge branch 'main' into fix/checkout-button"
19. commit d51c7e8: fix/checkout-button squash-merged into main "Fix checkout button after cart edits (#134)" (highlighted)
```

*Three changes, three short branches. Each started from main, went through a pull request and came back as one squash commit. Every merge into main was deployed within minutes.*

**Sofia:** Emma's copy, Daniel's threshold, Daniel's checkout fix. None of those branches lived longer than about a week.

**Daniel:** That's the point. `main` is always a version we could put on the site right now. Anything unfinished waits on its own branch, or, as you'll see today, behind a switch.

**Alex Tutor:** So the loop is short: a small branch from `main`, a pull request, a merge, a deploy. Then the next change starts again from fresh `main`.

![A loop of conveyors: a cat-food parcel leaves a side bay, joins the main belt, passes an inspection gate and rolls into a van as the next parcel starts.](https://productovi.com/courses/git-for-product/inline/github-flow-illustration-2.webp)

> **Exercise:** Put the steps of GitHub flow at CatChow in order, from the idea to customers seeing the change.

## Merged, then deployed

**Sofia:** On Wednesday Daniel said "give it five minutes". What happens in those five minutes?

**Daniel:** A GitHub Actions workflow takes the new `main`, builds the site and puts it on our servers. That's the **deploy**. It's a job like `build` and `test`, except it runs on `main` after a merge.

```text
#134 on Wednesday morning
- [Merged] Fix checkout button after cart edits #134: Merged into main at 9:40 as d51c7e8
- [All checks have passed] deploy (production): Ran on d51c7e8 in main, finished at 9:44
```

**Alex Tutor:** So "merged" and "deployed" are two events. At CatChow they're minutes apart, and the second follows the first on its own. In other teams they can be weeks apart.

**Alex Tutor:** You can check it without asking anyone. As of 2026 the repository page has a `Deployments` section in the right column. Click `production` to see which commit is live and since when.

**Sofia:** And if the deploy fails?

**Daniel:** Then `main` has the change and the site doesn't. We get an alert and either fix it right away or revert. A broken `main` blocks everyone's next deploy, so it jumps the queue.

**Sofia:** So before I tell support that something is live, I check `production` under `Deployments`.

**Alex Tutor:** That, and one look at the live site itself. The deploy tells you the code arrived; your own eyes tell you it works.

> **Exercise:** CatChow deploys every merge into `main` automatically; a deploy takes a few minutes. The moment #134 shows `Merged`, every customer already sees the fix on the site.

**Alex Tutor:** Daniel mentioned failed deploys. Here's what that looks like from the customer's side.

> **Exercise:** Pull request #160, a new delivery page text, shows `Merged`. A minute later the deploy to `production` fails. What do customers see on the delivery page?

## Small and short-lived

**Sofia:** Why keep branches so short, though? Wouldn't it be easier to build the whole feature and merge it once?

**Alex Tutor:** Remember `feature/loyalty-points`? Twelve commits ahead, 86 behind, untouched for five months. That's what "merge it once, at the end" tends to turn into.

**Daniel:** Every day a branch lives, `main` moves on without it. The further behind, the more chances of conflicts, or of a surprise like my checkout button in March.

**Alex Tutor:** Small pull requests are also reviewed faster and better. Forty changed lines take ten careful minutes. Two thousand wait for someone's free afternoon, and then get skimmed.

**Daniel:** And they're easier to undo. If one small change breaks something, `Revert` removes exactly that. Revert a huge one, and ten good changes leave with the bad one.

**Sofia:** So small isn't about tidiness. It's less risk per merge.

![A worker carries small light boxes across a narrow footbridge one at a time while another struggles to balance one giant wobbly crate.](https://productovi.com/courses/git-for-product/inline/github-flow-illustration-3.webp)

*Small pull requests are reviewed faster and are easier to undo.*

**Alex Tutor:** Exactly. A rule of thumb many teams use: a branch that lives a few days, and a pull request someone can review in one sitting.

> **Exercise:** Why does CatChow prefer small pull requests on short-lived branches? Select all that apply.

**Sofia:** Can I spot a branch like that before it becomes a problem?

**Alex Tutor:** On the branches page, yes. Look at two things: when the branch was last updated, and how far behind `main` it is. Old and far behind is the warning sign.

**Daniel:** And at its pull request. If `Files changed` says 60 files for "a small tweak", ask why. A question early is cheaper than a painful review later.

**Alex Tutor:** On a graph it's the same story: a long lane that started high up, while `main` kept collecting commits next to it.

> **Exercise:** Both branches are about to get a pull request. Which statement fits GitHub flow best?

**Alex Tutor:** The old loyalty branch is the warning sign. So how would you rebuild it?

> **Exercise:** Loyalty points have to be rebuilt on fresh `main`. Which plan fits GitHub flow?

## Merge now, switch on later

**Sofia:** Now Emma's banner. The sale starts on Monday. If she merges today, the banner goes live today and announces a sale that hasn't started.

**Alex Tutor:** And if she waits until Monday morning, the review, the merge and the deploy all happen in a rush while customers are already arriving.

**Daniel:** That's what a **feature flag** is for: a switch around a piece of the site. The code is in `main` and deployed, but the banner shows only while the flag is on.

![A fully installed but dark banner frame hangs in a shop window; beside it, a hand reaches for a wall switch in the off position.](https://productovi.com/courses/git-for-product/inline/github-flow-illustration-4.webp)

*With a feature flag, deployed and visible are two separate moments.*

**Alex Tutor:** At CatChow the switches live in a flag settings panel, outside the repository. Turning one on needs no pull request and no deploy.

**Daniel:** I added the check to Emma's branch: flag `autumn-sale-banner`, off by default. Emma marked #136 ready for review, I approved it, and she merged it an hour ago.

> **Exercise:** On Saturday a customer opens the CatChow home page. What do they see?

**Alex Tutor:** One more thing about Monday morning. True or false?

> **Exercise:** At CatChow, to show the sale banner on Monday at 8:00, Emma has to open a pull request that switches the flag `autumn-sale-banner` on and wait for the deploy.

**Sofia:** So the banner is on the site, but nobody can see it.

**Alex Tutor:** Merged, deployed, hidden. On Monday at 8:00 Emma switches the flag on, and customers see the banner within seconds. If something looks wrong, she switches it off, again with no deploy.

![The sale banner was merged and deployed on Friday behind a flag, so customers saw nothing until the flag went on on Monday at 8:00.](https://productovi.com/courses/git-for-product/diagrams/m4-l1-merged-deployed-visible.png)

**Daniel:** Flags also let us merge unfinished work. The half-built part stays switched off, so `main` stays deployable while a feature grows in small pull requests.

**Alex Tutor:** One catch: every flag is an extra "if" in the code. Once a feature is on for good, someone removes the flag, or the code fills up with forgotten switches.

**Sofia:** Someone from marketing will ask me whether the banner is live the moment they see #136 merged.

**Alex Tutor:** Then answer so it can't be misread. "Yes" is wrong, and "no" is only half the truth.

**Daniel:** And please mention the time. People plan newsletters around it.

**Alex Tutor:** Two or three sentences, as a chat reply.

> **Exercise:** A marketing colleague writes: "#136 is merged, so the sale banner is live? Can I send the newsletter tonight?" The sale starts on Monday; the flag `autumn-sale-banner` is off and Emma switches it on at 8:00 on Monday. Write your 2–3 sentence reply.

## Loyalty points, take two

**Sofia:** The CEO asked about loyalty points again today. Can Daniel just merge the old branch now?

**Daniel:** Not a chance. It's 86 behind, and half of what it touches has changed since. I'll rebuild it on fresh `main` in small pull requests, all behind one flag: `loyalty-points`.

**Alex Tutor:** Each piece is merged and deployed on its own, invisible to customers. The feature is "launched" only on the day the flag goes on for them.

**Daniel:** Could you draft how we slice it? You know what customers need first. I'll tell you what's technically possible.

**Sofia:** A slice plan, then: the pieces, their order, what stays hidden, and when we call it launched.

> **Exercise:** Draft the slice plan for loyalty points in GitHub flow.

## A feature in the pull request list

**Alex Tutor:** Wednesday next week. The team took your plan and added a `loyalty` label. Filter the pull requests by it, and the feature's progress reads itself.

```text
Pull requests with the label loyalty
- [Merged] Add loyalty points behind a flag #155: by Daniel · merged on Tuesday · flag loyalty-points: off
- [Open] Earn points on paid orders #156: by Daniel · Review required · checks passed
- [Draft] Write loyalty programme page #157: by Emma · Draft
```

**Sofia:** One piece is in `main` and on the site, but hidden. One is waiting for a reviewer. Emma's page is still a draft.

**Daniel:** And that's why nobody should announce anything yet, even though loyalty code has been live since Tuesday.

**Alex Tutor:** The pull request list shows how far the building has got. The flag shows what customers see. To answer "where is the feature?", a PM reads both.

**Sofia:** And a merged pull request with the flag off means "done and hidden", not "launched".

> **Exercise:** Match each thing you can see about loyalty points with what it tells you.

**Alex Tutor:** That's GitHub flow: `main` always deployable, small branches, a pull request for everything, deploy on merge, and flags for anything that isn't ready for customers.

**Sofia:** Support asked me today which version the checkout fix went out in. We don't have versions, do we?

**Daniel:** Not yet. Some teams live by version numbers and release branches. Next lesson you'll see one, and why we don't work like that.

- Previous lesson: [Icons, colours and checks: what every GitHub state means](https://productovi.com/courses/git-for-product/icons-and-colours.md)
- Next lesson: [Git flow: develop, release and hotfix branches](https://productovi.com/courses/git-for-product/git-flow.md)

---
Productovi Learning. Free, no sign-up needed to read.
