TL;DR: Fetch downloads refs and changes nothing in your working tree; pull fetches and integrates according to its flags and configuration; fast-forward-only refuses divergence. Rebase replays your commits onto a new base, which creates new commits with new hashes, so it is right for your own unpushed work and wrong for any branch someone else has already pulled.
How to approach it
Get the first half done in two sentences, because it is the easy mark. Spend the answer on what rebase does to commit identity, since that is the mechanism every follow-up depends on.
A strong answer
Fetch updates your remote-tracking refs. After it, origin/main reflects the remote and your local main is untouched, so you can inspect what arrived with git log main..origin/main before deciding anything. Pull fetches and then follows the selected integration policy: --ff-only refuses divergence, --rebase replays local commits, and --no-rebase selects merging. Defaults differ across Git versions and configuration, so specify the policy in automation. The practical advice is that fetch then look then integrate is the habit worth having, because pull integrates before you have seen what you are integrating.
Rebase is the interesting half. It does not move your commits; it replays them. Each commit is reapplied onto the new base and a new commit object is written, with a new hash, a new parent, and a new committer date. Original commits can remain reachable through other branches or reflogs; they become eligible for garbage collection only after those references expire. That is the whole basis of the rule: your commits now have different identities, so anyone who already has the old ones has a divergent history that Git will try to reconcile by duplicating your work.
So my working rule. Rebase your own feature branch onto main before opening a pull request, to get a linear history and to resolve conflicts once in your own context rather than inside a merge commit. Merge when integrating that branch into main, so the integration point is recorded and revertable as a unit. Never rebase a branch that others have based work on, and never force-push a shared branch.
If it has already happened, be direct about it: first preserve local work on a backup branch and save uncommitted changes. Identify the old upstream tip and the developer’s unique commits, then replay only those onto the rewritten upstream, for example with git rebase --onto. A blind pull/rebase can reintroduce intentionally removed commits. Resetting to the remote is appropriate only after preserving any work that must survive. What does not work is pretending it did not happen, because the duplicate commits will show up in the next merge.
The senior framing on the wider argument: the linear-versus-true-history debate is a team convention, not a correctness question, and it should be settled once and enforced by the platform rather than relitigated per pull request. Squash-and-merge is the third option and often the right default, because it gives main one commit per change while leaving the messy history on the branch. It also makes revert trivial, which matters more during an incident than history purity does.
What interviewers probe next
"What does force-with-lease do that force does not?" It checks that the remote ref still equals an expected value. The shorthand usually uses a remote-tracking ref, which a background fetch can advance without your review. For an agreed rewrite, record the reviewed old tip and use an explicit expected SHA; Git documents this limitation. A lease is not permission to rewrite shared history.
"How would you undo a bad rebase?" If it has not expired, git reflog identifies the pre-rebase HEAD. Create a recovery branch there first and save current work before considering a reset. The reflog is the answer to most "I destroyed my history" questions and knowing it exists is a real signal.
"When is a merge commit actually useful?" When you want to revert an entire feature as one operation, and when you want the record that these commits arrived together.
Common mistakes
Saying rebase gives "cleaner history" with no mention of hash rewriting. The cleanliness is the effect; the rewrite is the mechanism, and every risk comes from it.
Treating the rule as a superstition rather than explaining why shared branches break.
Not knowing the reflog, which turns a recoverable mistake into a real loss.