The other answers are very good, but I'd like to come at this from a few other angles that you may not have considered.
A single-pass compiler needs a language designed for that
If you compile in a single pass over the code, from source text to generated code, then you need all of the information to generate that code at the point where you parse it.
Such compilers have been extremely successful; the Borland "Turbo" compilers are famous examples. But they do require one important feature that not all languages have: You need to declare everything before you use them, such as forward-declared functions.
While C and Pascal have this property, many modern languages do not. Java and C# allow you to use a class member before it is declared, without forward declarations, so a single-pass compiler is simply not possible. And many other languages effectively require retaining some of the earlier source code, such as type inference or C++ templates.
But even so, there is always some tree (or more general graph) structure required, if only to store things like type information. If you have structs/records with structs/records as members, then you have a recursive structure, and the best way to represent that is as a tree of some kind.
Character-by-character processing is expensive
Consider a modestly-sized identifier, like print. Even though that's only 5 bytes long, anything that has to iterate over the characters in the identifier's name is always going to be more expensive than manipulating a single pointer. And it adds up; the pass over the program that has to deal with individual characters is usually one of the more expensive linear-time passes that a compiler has to do.
Program text is not only expensive to manipulate, but also expensive to store. Now it's true that a lot of thinking on compiler frontend design is partly informed by the more limited resources on a typical computer of the past, but even today, this is still a consideration.
In many popular interpreted languages of today like JavaScript or Python, the compiler frontend runs on the same machine, even in the same process, that is executing the program. And this could be on a device with limited performance, such as a mobile device, or a virtual machine provisioned from a cloud provider, where having more RAM or more CPU speed might mean mean more cost at scale.
It's true that today, virtual address space is extremely cheap, even on quite modest machines, so you can memory-map input files instead of storing them, assuming the source program comes from a file. Nonetheless, the less time a compiler spends scanning characters, the better.
A compiler is one part of a toolchain
The final thing you might want to consider is all of the other software development tools that a programmer might want or expect. Even leaving JIT compilation or linking to one side, consider debuggers, profilers, coverage analysers, and so forth.
Maybe you can generate code in a single pass from the input string more or less directly. But a compiler is, realistically, only one product in a product line of tools.
This goes double if you want to reuse existing tools. I mean, maybe you are happy with the existing state of debuggers. Fine. But you still have to generate whatever those tools need as output, as well as generating code. And that will either complicate your code generator, or usually require retaining some data about the structure of the program for use in a subsequent pass.