A code table is only useful if you can read a symbol back out of the bitstream. Today you decode symbols by walking bits one at a time until they match a code, the inner loop of all Huffman decoding.
Decode symbols from the bit reader against a canonical code table by accumulating bits until a code matches.
Decoding reverses the assignment. You read one bit at a time, growing a candidate code with code = (code << 1) | bit and tracking its length, and after each bit you ask: does any symbol have exactly this length-and-code? Because the codes are prefix-free, the first match is unambiguous - there is never a choice. Reading 0 matches B immediately; reading 1 then 0 matches A; reading 1,1,1 matches D.
Watch the bit order carefully: Huffman codes are consumed most-significant-bit-first into the accumulator, even though the bit reader itself hands you bits low-first from each byte. That distinction trips everyone once - the byte-level order and the code-level order are different rules that coexist. A simple linear scan of the table per bit is perfectly fine for a teaching decoder; real libraries use lookup tables for speed, but the behavior is identical. This one function decodes every symbol in every Huffman block from here on.
// read one bit at a time, building: code = (code << 1) | bit; length++.// after each bit, check whether any symbol has exactly this (length, code).func decodeSymbol(r *BitReader, table HuffTable) int { }