In Go, what does a shift count larger than the operand's width produce, and what does a negative count do?
answer
- the count is not reduced at all
- shifting past the width stays legal
- the left operand picks the fill bit
- a negative count is a run-time panic
basics
~20 sGo does not mask shift counts. Shifting a uint64 left by 64 or more yields 0, and shifting a negative signed value right far enough yields -1. A shift count that is negative at run time panics.
solid answer
~50 sGo defines a shift as if the operand were shifted one bit at a time, `n` times, with **no upper limit and no masking** of the count. So for a `uint64` variable `x`, `x << 64` is `0`, and for an `int8` holding `-1`, `y >> 20` is still `-1`, because a right shift of a signed operand is arithmetic and keeps feeding in the sign bit. That differs from languages that reduce the count modulo the operand width, where `x << 64` would give back `x`. The shift count may be any integer type, signed or unsigned, but it must be non-negative **at run time** — a negative count panics rather than reversing direction. Go also has no `>>>` operator: whether a right shift is arithmetic or logical is decided by the signedness of the **left** operand, so you convert to an unsigned type when you want zero fill.
code
go · 8 linesvar x uint64 = 1
fmt.Println(x << 64) // 0 - the count is not reduced modulo 64
var y int8 = -1
fmt.Println(y >> 20) // -1 - a signed right shift refills the sign bit
n := -1
_ = x << n // run-time panic: the count must not be negativego deeper
Remember that a left shift multiplies by powers of two and that Go writes them as << and >>. The one fact to carry away is that shifting a value further than its own width is allowed and gives you zero.
Explain the one-bit-at-a-time definition and derive both results from it: no masking, and an over-long arithmetic right shift saturating at -1 for a negative operand. Say why the left operand's type, not the operator, picks the fill.
Treat a data-derived shift count like an index: state that an oversized count is defined and harmless while a negative one panics, and show where you would validate it in a parser or protocol decoder.
Argue why a defined over-wide shift is worth more than a faster undefined one: it makes decoder behaviour reproducible across architectures and compiler versions, which is what lets you fuzz it and trust the result.
## The definition to memorise Go's spec defines `<<` and `>>` as shifting the left operand by the count given by the right operand, **as if shifted one bit at a time, `n` times**. Three consequences fall straight out of that phrasing: 1. **There is no upper limit on the count.** Shifting a 64-bit value by 64, or by 1000, is perfectly legal and yields 0 for a left shift. 2. **The count is never masked.** Many languages reduce the count modulo the operand width, so a 64-bit `x << 64` gives back `x`. In Go it gives `0`. This is the single most common wrong answer. 3. **The count must be non-negative at run time.** A negative count is a run-time panic, not a shift in the other direction and not a huge unsigned count. ## Arithmetic versus logical, decided by the left operand Go has no separate `>>>` operator. The **type of the left operand** decides the kind of shift: - Signed left operand: `>>` is an **arithmetic** shift, filling from the left with copies of the sign bit. `-1 >> 20` is `-1`; `-8 >> 1` is `-4`. - Unsigned left operand: `>>` is a **logical** shift, filling with zeros. `uint8(0xF0) >> 4` is `0x0F`. This is why converting is the idiom: when you have a signed value and you want zero fill, you write `int32(uint32(v) >> 3)` rather than reaching for an operator that does not exist. Left shifts are the same in both cases — bits vacated on the right are filled with zeros, and bits shifted off the top are simply discarded. ```go var x uint64 = 1 fmt.Println(x << 64) // 0 - no masking, the bit is shifted out var y int8 = -1 fmt.Println(y >> 20) // -1 - arithmetic shift keeps refilling the sign bit ``` Note what the second line means: for a signed operand, an over-long right shift **saturates**, to `-1` for a negative value and `0` for a non-negative one. It does not become 0 in every case. ## The shift count's type In current Go the shift count may be any integer type, signed or unsigned, or an untyped constant that is representable as an integer. That was not always so — older Go required an unsigned count and forced conversions like `x << uint(n)` all over otherwise clean code. Encountering that pattern in an old file tells you the code predates the relaxation, not that the conversion is required. What did **not** change: the count must be non-negative when the shift executes. ```go n := -1 _ = x << n // panics at run time: the shift count must not be negative ``` A negative count is caught at compile time only when the count is a constant; when it arrives in a variable — computed from a bit position, a field width read from a header, a length minus an offset — the check happens at run time and the failure is a panic with a stack trace. If your shift counts are derived from data, validate them the way you would validate a slice index. ## Constant shifts are a different world When both operands are untyped constants, the shift happens in the compiler's arbitrary-precision arithmetic, and the constraint moves to the target type: ```go const big = 1 << 62 // fine, fits in an int on a 64-bit platform // const huge = 1 << 64 // constant overflows int, if it must become an int var f float64 = 1 << 70 // fine, the constant becomes a float64 ``` So a constant shift does not silently produce 0 the way a variable shift does; it either fits the type it must have or the build fails. Keeping this straight matters when you are writing size constants — `1 << 30` for a gigabyte-sized limit is idiomatic and safe, while the same expression with a variable exponent needs a range check. ## Where the difference shows up The practical cases are bit-field and protocol code: extracting `width` bits at `offset` with `(v >> offset) & (1<<width - 1)`. If `offset` or `width` comes from parsed input, both a negative value and an oversized value are reachable. In Go the oversized one is *safe and defined* — you get 0, or `-1` for a negative signed operand — while the negative one panics. Compare that with C, where an over-wide shift is undefined behaviour and may produce anything at all: Go's rule is deliberately boring, which is what you want in a parser. The interview version of the question is usually one line: what does `x << 64` give for a `uint64`? Answer `0`, say that Go does not mask the count, and add that a negative count panics. That is the whole thing.
- Why does Go have no >>> operator?Because the signedness of the left operand already decides the fill. A right shift of a signed value is arithmetic and refills the sign bit; a right shift of an unsigned value is logical and refills with zeros. If you want zero fill on a signed value you convert — `int32(uint32(v) >> 1)` — which makes the reinterpretation visible at the point it happens instead of hiding it in a third operator.
- What is the difference between a constant shift and a variable shift that overflows the width?A constant shift is evaluated in arbitrary precision at compile time, so `1 << 64` is a valid number that simply may not fit the type it is assigned to — you get a build error like constant overflows int. A variable shift is a run-time operation on a fixed-width value, so shifting past the width is legal and defined, yielding 0 for a left shift.
- If a shift count comes from parsed input, what do you check?Only the lower bound in practice. An oversized count is defined in Go — it produces 0, or -1 for a negative signed operand under an arithmetic right shift — so it is a wrong answer rather than a crash. A negative count panics, so if the count is computed by subtraction, or read into a signed field, guard it exactly the way you would guard a slice index.
saying these in an interview costs you the question
- Assumes the shift count is reduced modulo the operand width
- Expects a negative count to shift the other way
- Looks for a >>> operator for a logical right shift
- Says shifting past the width is undefined behaviour
- Thinks an over-long right shift of a negative value gives 0