Always code without generating any "runtime garbage"
Garbage generation is one of the most important causes for poor performance in Unity games, and it can easily be avoided if you understand how to work around it.
Details here: Garbage Generation 101
Coroutines create tons of garbage if not used properly
Concave mesh colliders are expensive and sometimes inaccurate
PhysX only partially supports concave colliders, because they are too costly compared to a setup of multiple primitives and/or convex colliders, and so it never truly makes sense to use them. That means that not only are concave colliders very expensive, but they will also sometimes yield incorrect results when raycasted, overlapped, or collided with.
Don't forget to make GameObjects static whenever you can
Anything that doesn't move should be static. This saves on rendering, physics, and transform hierarchy performance.
Always store references to your components instead of doing GetComponent<>() every frame
Self-explanatory.
Always use C# delegates and actions instead of UnityEvents
More details here
sharedMaterial and sharedMesh
todo
Player and Graphics settings
todo: Graphics API, Rendering path, GPU skinning, graphics jobs, etc....
Use ScriptableObjects to save memory
See ScriptableObjects 101 for more details