In a one-pass assembler, the program reads the source code line-by-line. The program assembles the line to the best of its ability, and if there is a forward reference, it leaves a placeholder and saves the location for later. Then the forward reference can be resolved, either when the undefined label is defined, or at the end when all labels have been collected.
In a two-pass assembler, the first pass just collects all the labels and their addresses. However, to know the addresses, it needs to know the sizes of the instructions. To determine the size, it needs to lexically analyze the line, parse it, and check that it is valid code. Then it can calculate the size.
If you're going to make all that effort, you might as well assemble the line completely and write the binary to the output. If you don't, you have to repeat the lexing/parsing/code generation process once we're in the 2nd pass which seems really silly and wasteful. The only lines that can't be assembled are ones with forward references, but that's the case for one-pass assemblers too. It seems like a two-pass assembler has practically the same implementation as a one-pass assembler.
Am I just misunderstanding something?