MediumBest practice
Your branch has forty commits, half of them "wip" and "fix typo". What do you do before opening the PR?
Reshape the history into commits a reviewer can read one at a time — an interactive rebase to squash the noise into the change it belongs to, reorder so each commit is coherent, and rewrite messages to say why rather than what. Aim for commits that each build and pass tests. Then force-push with `--force-with-lease` so you cannot clobber someone else's push.
The goal is not tidiness for its own sake; it is that a reviewer can follow "move the file", then "change the behaviour", instead of a diff where both happened at once. That said, the effort scales with the change — a three-commit fix needs nothing. `--force-with-lease` over `--force` is the detail worth stating: it refuses the push if the remote moved since you last fetched, which is what stops you silently deleting a colleague's commit on a shared branch.
EasyCode review
Review this pull request description
Title: fixes
Description:
(empty)
14 files changed, 380 insertions(+), 120 deletions(-)
A reviewer has to reverse-engineer intent from the diff, which is the slowest and least reliable way to review. The description should say WHY the change exists (the bug, the ticket, the constraint), WHAT approach was taken and what alternatives were rejected, and HOW it was verified — because the diff already shows what changed but can never show the reasoning. At 380 lines across 14 files it should also probably be split, separating any refactor from the behaviour change so each can be reviewed properly. The title should describe the change, not its category; "fixes" tells a future reader nothing when they find this commit via blame.
The commit and PR text is the only place the reasoning survives — the diff shows what changed and can never show what was considered and rejected, or why the obvious approach did not work. Six months later that text is what someone reads before changing the same code. Size matters too: 380 lines across 14 files mixing a refactor with a behaviour change cannot be reviewed carefully as one unit, and should be split.
EasyConcept
What IS a branch in Git, and why does that explanation make merge, rebase and reset make sense?
A branch is just a movable pointer to a commit; a commit is an immutable snapshot with a link to its parents. Almost every confusing Git operation becomes obvious once you hold that model. Creating a branch is writing a new pointer — which is why it is instant and cheap. Committing moves the pointer forward. `merge` creates a commit with two parents, so both histories are preserved. `rebase` replays your commits onto a new base, producing NEW commits with new hashes — which is exactly why it is dangerous on a branch others have pulled. `reset` just moves the pointer, which is why the orphaned commits are still reachable through the reflog. Deleting a branch removes a pointer, not the commits.
Holding the pointer model turns the confusing commands into consequences rather than incantations. Branch creation is instant because it writes one file containing a hash. Rebase produces different commit hashes because a commit includes its parent, so replaying onto a new base necessarily creates new commits — which is exactly why rewriting shared history is disruptive and why reflog can still recover what you thought you lost.
MediumFollow-up
You recommended feature flags instead of long branches. What new problems does that create?
Flags move complexity from merge time to runtime. Every live flag doubles the paths through the affected code, so testing has to cover combinations rather than one state, and a bug can depend on a flag combination nobody tried. Flags also become permanent if nobody removes them: within a year the codebase has dozens of stale conditionals that no one dares delete because the flag state in production is unclear. And unlike a branch, flagged code SHIPS — incomplete work is in the bundle and reachable, so it must be safe to have present even when off. Mitigations are an owner and expiry per flag, a default-off safe behaviour, cleanup treated as part of finishing the feature, and a cap on how many flags may be live in one area.
The "flagged code ships" point is the one with security implications, and it is often overlooked entirely.
MediumMultiple choice
What is the main trade-off of squash-merging a pull request?
A tidy main history where each commit is a complete, revertible unit — at the cost of intermediate steps. That is a good trade when the intermediates are "fix typo" noise, and a bad one when they were meaningful, reviewable stages.
HardScenario
A merge conflicts in a file that neither branch appears to have edited. What is actually going on?
Usually one side moved or renamed the file while the other modified it, and Git's rename detection either matched it to the wrong file or failed because the content changed too much alongside the move. The other common causes are a whole-file rewrite from a line-ending or formatting change, so every line looks modified, and a submodule or generated lockfile that both sides regenerated. `git log --follow`, `git diff -M --stat` on the merge base, and `git merge --stat` tell you which.
The reason this is confusing is that "neither branch touched it" is nearly always false at the content level — a project-wide Prettier run or a CRLF change touches every line of every file, so any concurrent edit conflicts with all of it. Once you know which case you are in the resolution is mechanical: for a rename-plus-edit, take the moved file and reapply the edit; for a formatting rewrite, merge against the pre-format commit and reformat afterwards; for a lockfile, regenerate rather than hand-merge.
EasyBest practice
What makes a commit message useful six months later?
A short imperative subject saying what changed, and a body explaining WHY — the constraint, the bug, the decision. The diff already shows what; it can never show the reasoning or the alternatives rejected. Linking the issue or incident gives the next reader the full context.
The best signal is whether the candidate mentions "why" at all — most answers stop at formatting conventions.
HardConcept
Trunk-based development versus long-lived release branches — what actually drives the choice?
Deployment cadence and the cost of a bad release. Trunk-based means short-lived branches merged daily, with incomplete work hidden behind flags; it minimises merge pain and keeps integration continuous, but it demands strong CI, feature flags and the ability to deploy often. Long-lived release branches suit products that ship on a schedule, support multiple versions in the field, or need a stabilisation period — desktop software, regulated environments, anything with a customer-managed upgrade. The cost there is merge divergence and the parallel maintenance of fixes across branches. The deciding question is whether you can deploy a fix quickly: if you can, trunk-based is cheaper; if a bad release is expensive to correct, the stabilisation branch earns its overhead.
Framing it as a consequence of release economics rather than a matter of taste is what makes this answer useful.
The remaining 8 Git questions — plus mock interviews, spaced revision and progress tracking — are in the free interview prep workspace.