What does hash.Hash's Sum(b []byte) method do to b and to the hash's internal state?
answer
- the argument is not the input
- think append, not allocate
- the hash is still usable afterwards
- nil is just an empty destination
- Reset is a separate call for a reason
basics
~20 sSum appends the current digest to the slice b and returns the extended slice; it does not hash b and does not change or reset the running hash. h.Sum(nil) is the usual form and returns just the digest.
solid answer
~40 s`Sum(b []byte) []byte` treats its argument as a **destination to append to**, exactly like the `append` builtin: it returns `b` followed by the digest of everything written so far. `h.Sum(nil)` is the idiom, and it gives you a fresh `[]byte` of `h.Size()` bytes. Handing it a non-empty slice is occasionally useful - `h.Sum(prefix)` gets you the prefix and the digest in one allocation. The second half matters more: `Sum` **does not modify the hash state**. You can call it mid-stream, keep writing, and call it again - the second result covers everything written, not just the bytes since the first call. That also means reusing a `hash.Hash` for a new message requires an explicit `Reset()`; forgetting it silently digests the concatenation of both inputs.
code
go · 12 linesh := sha256.New()
h.Write([]byte("a"))
d1 := h.Sum(nil) // digest of "a"
h.Write([]byte("b"))
d2 := h.Sum(nil) // digest of "ab": Sum did not reset anything
// Sum appends to whatever slice you hand it.
out := h.Sum([]byte("sha256:"))
// Reset returns the hash to its initial state for a new message.
h.Reset()go deeper
Learn the everyday form, h.Sum(nil), which hands back the digest as a []byte. The one thing to fix in your head: the argument is a destination the digest is appended to, never data to be hashed.
State both halves precisely - Sum appends to the slice you pass, and it leaves the running state untouched - and explain that the second half is why reusing a hash across inputs needs an explicit Reset.
Point at the bug this API shape produces in real code: a looped or pooled hash that is never reset, yielding digests over concatenated inputs that still look like perfectly valid hex. Say how you would catch it.
Decide whether your codebase wraps this API at all. A helper that takes a reader and returns a digest removes a whole class of state bugs, at the cost of one more thing every newcomer has to learn instead of the standard interface.
## The signature that surprises people ```go type Hash interface { io.Writer Sum(b []byte) []byte Reset() Size() int BlockSize() int } ``` Read naively, `Sum(b []byte)` looks like it hashes `b`. It does not. The doc comment is precise: *Sum appends the current hash to b and returns the resulting slice. It does not change the underlying hash state.* Two independent facts live in that sentence, and both are load-bearing. ### Fact one: the argument is a destination `Sum` behaves like `append`. It writes the digest onto the end of whatever slice you pass and returns the grown slice. ```go h := sha256.New() h.Write(body) d := h.Sum(nil) // 32 bytes: just the digest out := h.Sum([]byte("v1:")) // "v1:" + 32 raw bytes, one slice ``` The `nil` form is overwhelmingly the common one: `nil` is a perfectly good empty slice to append to, so `Sum(nil)` allocates a fresh 32-byte result. The non-nil form exists so you can build a framed value without a second allocation, or reuse a scratch buffer across a loop (`buf = h.Sum(buf[:0])`). Ordinary `append` rules apply: if the destination has spare capacity the digest lands in place, otherwise a new array is allocated. The reason the API is shaped this way at all is allocation control. `hash.Hash` is used in hot paths, and a method that always allocated its own return value would be impossible to tune. ### Fact two: the hash keeps running `Sum` computes the finalisation - the padding and length encoding SHA-256 appends before producing its output - on a **copy** of the internal state. Your hash is untouched. ```go h := sha256.New() h.Write([]byte("a")) d1 := h.Sum(nil) // digest of "a" h.Write([]byte("b")) d2 := h.Sum(nil) // digest of "ab", not of "b" ``` This is genuinely useful: you can emit a running digest at checkpoints while continuing to consume a stream. It is also the source of a quiet class of bugs. A loop that hashes many inputs with one reused hash: ```go h := sha256.New() for _, item := range items { h.Write(item) digests = append(digests, h.Sum(nil)) // wrong: each covers all prior items } ``` Every digest after the first is over a growing concatenation. Nothing errors. Every value is a well-formed 32-byte digest and encodes to plausible hex. The fix is one line - `h.Reset()` at the top of each iteration - and `Reset` is a separate method precisely because `Sum` deliberately does not do it. ### Reset, Size and BlockSize - `Reset()` returns the hash to the state it had immediately after construction. For a keyed hash built by `hmac.New`, it resets to the initial **keyed** state - the key is not forgotten. - `Size()` tells you how many bytes `Sum` will append: 32 for SHA-256, matching the `sha256.Size` constant. Write generic code against `h.Size()` rather than a literal. - `BlockSize()` is the algorithm's internal block width (64 for SHA-256) and matters mainly to constructions layered on top of a hash. ### What Sum is not - It is not a comparison, an encoder, or a reset. - It does not consume its argument as input. `h.Sum(data)` is a common misreading and produces a digest that ignores `data` entirely while prepending it to the output. - Its result is not a fixed-size array. If you want a comparable value you must copy into one, or use the one-shot `sha256.Sum256` instead. ### The habit to build Read `h.Sum(x)` as "give me `x` with the digest stuck on the end, and leave the hash alone." Then, whenever a `hash.Hash` outlives a single message - a loop, a long-lived struct field, a pooled value - ask where the `Reset()` is. If you cannot point at it, the digests are wrong.
- What exactly comes back from h.Sum([]byte("sha256:")) on a SHA-256 hash?A single `[]byte` of length 39: the seven bytes of `"sha256:"` followed by the 32 raw digest bytes. Ordinary `append` semantics apply, so if that slice had spare capacity the digest is written in place, otherwise a larger array is allocated. The digest bytes are binary - encode them before they reach a log or a filename.
- You reuse one hash.Hash across many messages in a loop. What must you call, and why isn't Sum enough?`Reset()` at the start of each message. `Sum` is documented not to change the hash state, so without a reset the second message's digest covers message one and message two concatenated. The failure is silent: every digest is still 32 well-formed bytes, so only a comparison against an independently known value exposes it.
- How do you know how many bytes Sum will append without hardcoding 32?Call `h.Size()`. It is part of the `hash.Hash` interface and returns the digest length for whatever algorithm is behind the interface - 32 for SHA-256, matching the `sha256.Size` constant, 64 for SHA-512. Generic helpers that accept a `hash.Hash` should size buffers from `h.Size()` rather than a literal.
saying these in an interview costs you the question
- Thinks Sum hashes the slice passed to it
- Thinks Sum resets the hash
- Believes the hash is unusable once Sum has been called
- Reuses one hash across messages without calling Reset
- Expects Sum to return a [32]byte array
- Hardcodes 32 instead of asking h.Size()