Of course you have to profile your queries and look at the execution plan. But the two main things that come up over and over again are filter out as much as you can as soon as you can and try to avoid cursors.
I saw an application where someone downloaded an entire database table of events to a client and then went through each row one by one filtering based on some criteria. There was a HUGE performance increase in passing the filter criteria to the database and having the query apply the criteria in a where clause. This is obvious to people who work with databases, but I have seen similar things crop up. Also some people have queries that store a bunch of temp tables full of rows that they don't need which are then eliminated in a final join of the temp tables. Basically if you eliminate from the queries that populate the temp tables then there is less data for the rest of the query and the whole query runs faster.
Cursors are obvious. If you have a million rows and go row by row then it will take forever. Doing some tests, if you connect to a database even with a "slow" dynamic language like Perl and perform some row by row operation on a dataset, the speed will still be much greater than a cursor in the database. Do it with something like Java/C/C++ and the speed difference is even bigger. If you can find/eliminate a cursor in the database code, it will run much faster... If you must use a cursor, rewriting that part in any programming language and getting it out of the database will probably yield huge performance increases.
One more note on cursors, beware code like SELECT @col1 = col1, @col2 = col2, @col3 = col3 where id = @currentid in a loop that goes through IDs and then executes statements on each column. Basically this is a cursor as well. Not only that but using real cursors is often faster than this, especially static and forward_only. If you can change the operation to be set based it will be much faster.....That being said, cursors have a place for some things....but from a performance perspective there is a penalty to using them over set based approaches.
Also beware the execution plan. Sometimes it estimates operations that take seconds to be very expensive and operations that take minutes to be very cheap. When viewing an execution plan make sure to check everything by maybe inserting some SELECT 'At this area', GETDATE() into your code.