For completeness, there are six distinct scopes. They are, in order of largest to smallest access: global, import, component, instance, closure, and local.
Global scope is only available in LWC OSS. Every component in every namespace can use values from this scope. As far as I'm aware, there's no component-accessible objects available at this scope in Salesforce. You cannot use truly global variables inside Salesforce. This scope's duration is effectively forever, as long as the app is loaded.
Import scope is storage used for an imported module. This will persist at least as long as any component that imports the module is loaded, and likely last as long as the Lightning App Runtime is loaded. This scope can be used for inter-component communication, such as pubsub, because all components share the same import scope.
Component scope is storage used for any objects declared outside of the class. It exists as long as any instance of a specific component is loaded. It will last at least as long as any component instances are loaded, so it may be shorter than import scope's duration.
Instance scope is storage used for a specific instance of a component, declared inside the class. You've called this "reactive properties," which would also be a true statement. Objects in this scope are only guaranteed to exist as long as that component isn't destroyed, typically shortly after
disconnectedCallback, though there is no specific information on when the memory will be freed.
Closure scope is storage used temporarily while a function is pending in some way. For example, if you use a Promise, a Closure is created that will last until the Promise is fully resolved. For a properly written component, this scope will exist longer than any local scope, but shorter than any of the other scopes, which is why it is placed here.
Local scope is storage used by a function, and will not exist after a function runs to completion. It is the shortest scope and has the smallest visibility. We use this to perform local calculations, etc.
The smaller the scope that data is placed in, the less accessible it is, so the more copies of that data may be necessary. So, from a Memory Usage and Run Time Performance perspective, placing an object in a larger scope will guarantee it is duplicated less, but it will also be in memory longer.
A library module is evaluated only once, even if imported in to many components. In other words, it is the most memory efficient and runtime efficient of all the options you have in Salesforce. Likewise, component scope variables are evaluated only once when the first instance of a component loads, so it is less efficient than import scope, but more efficient than instance scope. This property is true all the way down.
While it's rarely ever necessary to note, this means that you should not modify any variables stored at the component level; they're marked as
const to implicate the fact, but you can indeed use
let instead and have the variable modifiable by all instances. Since those properties are not reactive, this can have unintended side effects, so be careful.
You should always use the smallest scope possible to avoid side effects, but the largest scope possible to maximize performance. There is a definite tradeoff that you need to consider when designing your code. In most cases, instance scope is probably the correct scope to use. As long as you're aware of potential side effects, moving the scope upwards can improve performance significantly.
Also keep in mind that markup can generally only access instance-scoped data (reactive properties), so this limits the the other scopes mostly to handlers. You need to copy data from the larger scopes to a reactive property to use them locally, and since those larger scopes can be modified if you're not careful, you may end up needing to make copies for local use.