How Binary Search actually works
Binary search answers one question over and over: which half of the remaining window can I throw away? The array being sorted is what makes the answer provable every time - one comparison at the midpoint tells you the target cannot be in one entire half. That is why ten thousand elements need at most fourteen probes.
The window is the whole array
left = 0 and right = len(arr) - 1 declare the search window: the target, if present, lives between arr[left] and arr[right] inclusive. Everything the algorithm does from here is shrinking that window without ever letting the target escape it. That claim - 'the target is inside the window, if it is anywhere' - is the invariant, and the player checks it on every step.
Probe the middle, discard a half
mid = (left + right) // 2 picks the midpoint. If arr[mid] is too small, the target cannot be at mid or anywhere left of it - the array is sorted - so left = mid + 1 discards that whole half. Too large, and right = mid - 1 discards the other. The +1 and -1 matter: mid itself has been ruled out, and keeping it in the window is the classic way to loop forever.
The equality case ends it
When arr[mid] == target the index is returned immediately. Watch the window sizes on the way here: 10, then 5, then 2 - each probe halves what remains, which is the whole reason this runs in O(log n).
An empty window is a proof
The loop condition is while left <= right. When left crosses right, the window is empty - and because the invariant held at every step, an empty window PROVES the target was never there. Returning -1 is not giving up; it is a conclusion.
Common confusions
Why left <= right and not left < right?
With <=, a window of exactly one element (left == right) still gets probed. With <, that last element is skipped - and a target sitting there is falsely reported missing. The half-open convention (right = len(arr), while left < right) also works, but it changes every other line; mixing the two conventions is where the bugs live.
Does 'binary' mean binary numbers?
No - it means the search splits the window in two. The name describes the branching, not the number base.
Why must the array be sorted?
The discard step is a logical deduction: 'arr[mid] < target, therefore nothing at or left of mid can be the target.' Unsorted, that deduction is false and the algorithm silently returns wrong answers - it does not crash, which is what makes it dangerous.