Битовое отрицание в JS
Всё просто: побитовые операторы в js работают только со знаковыми (signed) целыми (integer) длиной в 32 бита (оператор >>> работает с unsigned). Иными словами, побитовые операторы интерпретируют операнды как последовательность из 32 битов.

Т.о., при выполнении ~3 число 3 представляется в виде 0000 0000 0000 0000 0000 0000 0000 0011, результат, понятное дело 1111 1111 1111 1111 1111 1111 1111 1100 — это -4. Если крайний левый (знаковый) бит равен 0 — число положительное, если равен 1 — число отрицательное. Максимальное знаковое целое, которое уместится в 32 бита — это 0111 1111 1111 1111 1111 1111 1111 1111, или 2147483647 (по основанию 10). Минимальное 32-bit signed integer — это 1000 0000 0000 0000 0000 0000 0000 0000, или -2147483648.

2147483647   0111 1111 1111 1111 1111 1111 1111 1111
100          0000 0000 0000 0000 0000 0000 0110 0100
5            0000 0000 0000 0000 0000 0000 0000 0101
4            0000 0000 0000 0000 0000 0000 0000 0100
3            0000 0000 0000 0000 0000 0000 0000 0011
2            0000 0000 0000 0000 0000 0000 0000 0010
1            0000 0000 0000 0000 0000 0000 0000 0001
0            0000 0000 0000 0000 0000 0000 0000 0000
-1           1111 1111 1111 1111 1111 1111 1111 1111
-2           1111 1111 1111 1111 1111 1111 1111 1110
-3           1111 1111 1111 1111 1111 1111 1111 1101
-4           1111 1111 1111 1111 1111 1111 1111 1100
-5           1111 1111 1111 1111 1111 1111 1111 1011
-100         1111 1111 1111 1111 1111 1111 1001 1011
-2147483648  1000 0000 0000 0000 0000 0000 0000 0000

~~3.14 // 3

Шаг 1: число приводится к целому и представляется в виде последовательности 0000 0000 0000 0000 0000 0000 0000 0011.
Шаг 2: производится сама побитовая замена 0->1 и 1->0. Получаем 1111 1111 1111 1111 1111 1111 1111 1100, или -4 (по осн. 10).
Шаг 3: производится ~(-4), то бишь получим в итоге 3.

Ну, с нулем всё понятно думаю.
На собеседованиях спрашивают потому, что ~~ — это быстрая альтернатива Math.floor для небольших чисел (которые влезают в int32).

jsperf.com/math-floor-vs-math-round-vs-parseint/74

Для выполнения Math.floor движку потребуется пройтись вверх до глобального пространства имен, чтобы найти там Math (вдруг кто определил переменную с таким именем?), получить свойство floor объекта (вдруг кто перезаписал его?), вызвать его как функцию, в которую передать параметр, затем вернуть значение.

В случае с оператором, который в JavaScript перегрузить нельзя, накладные расходы минимальны.

Единственное ограничение — разрядность. С современными timestamp'ами из Date#getTime, к сожалению, уже не сработает (будет переполнение).