First of all, whenever you have a “how it is used in practice” question, a good way to start is to search the Go source code (or any big enough Go code base, really), and the package docs for answers.
panic are quite different.
panic is used when the program, or its part, has reached an unrecoverable state.
panicis called, including implicitly for run-time errors such as indexing a slice out of bounds or failing a type assertion, it immediately stops execution of the current function and begins unwinding the stack of the goroutine, running any deferred functions along the way. If that unwinding reaches the top of the goroutine’s stack, the program dies.
os.Exit is used when you need to abort the program immediately, with no possibility of recovery or running a deferred clean-up statement, and also return an error code (that other programs can use to report what happened). This is useful in tests, when you already know that after this one test fails, the other will fail as well, so you might as well just exit now. This can also be used when your program has done everything it needed to do, and now just needs to exit, i.e. after printing a help message.
Most of the time you won’t use
panic (you should return an
error instead), and you almost never need
os.Exit outside of some cases in tests and for quick program termination.