Given a query with equality matches, a sort and a range, a compound index's fields should run Equality first, then Sort, then Range. Take "the twenty oldest shipped orders over $4,000": equality on status, range on total, sort on createdAt. Build both candidate orders and hint each in turn.
db.orders.createIndex({ status: 1, total: 1, createdAt: 1 }, { name: 'ers' });
db.orders.createIndex({ status: 1, createdAt: 1, total: 1 }, { name: 'esr' });
const q = { status: 'shipped', total: { $gt: 4000 } }; // 3,920 matching orders
for (const name of ['ers', 'esr']) {
const e = db.orders.find(q).sort({ createdAt: 1 }).limit(20).hint(name)
.explain('executionStats');
const s = e.executionStats, b = e.queryPlanner.winningPlan.inputStage.inputStage.indexBounds;
print(`${name}: ${s.executionStages.stage} keys=${s.totalKeysExamined} ` +
`docs=${s.totalDocsExamined} ms=${s.executionTimeMillis}`);
print(' ' + Object.keys(b).map(k => k + ' ' + b[k]).join(' '));
}ers: FETCH keys=3920 docs=20 ms=5 status ["shipped", "shipped"] total (4000, inf] createdAt [MinKey, MaxKey] esr: LIMIT keys=78 docs=20 ms=0 status ["shipped", "shipped"] createdAt [MinKey, MaxKey] total (4000, inf]
Fifty times fewer keys for the same twenty documents. Both indexes cover the same three fields; only the order differs.

Equality on status pins both scans to one contiguous block, and what comes next decides everything. Unhinted, the planner names esr the winner and lists ers among two rejectedPlans.
Three corollaries. Equality fields can go in any order among themselves — put the more selective first anyway, so the index also serves as a prefix. $in counts as equality: status: { $in: ['shipped', 'paid'] } gives a SORT_MERGE over one scan per value, and ten documents still cost 11 keys. $ne, $nin and $exists: false are not selective, so treat them as ranges and keep them last.