Junior software engineers tend to use straightforward and basic development techniques; the sort of techniques you might find taught in books and on many courses. These work, but many of them tend to be inefficient and slow. With more experience comes an urge to make things better, and to mature as an engineer:
When I was a child, I spoke as a child, I understood as a child, I thought as a child; but when I became a man, I put away childish things - 1 Corinthians 13:11
It's something of a deep quote, given the subject matter, but it seems relevant. What are the easiest ways to do this? Normally I'd recommend the usual cycle of "Measure and optimize", but there are a few general principles you can apply that will help you get the "low hanging fruit" that get overlooked a lot. With a little practice, you'll automatically incorporate these principles into your designs in future. There will be people who will argue that "Premature Optimization is The Root of All Evil", but as I described at System Performance – Part 1 – Outrun The Bear, that's a misquote and is almost always taken out of context.
Use Lazy Evaluation
When you develop a system, it seems obvious and easiest to create all of the data you need, or might need, as it starts. This is actually a very bad idea. Data takes time to create or load, and the more data you need, the longer it takes to create. By the time you've run calculations, retrieved data from databases and other remote storage and used all of this to build data objects, your users are frustrated and have already judged you poorly.
You can optimize object creation to a certain extent - asynchronous and parallel processing will get you some way towards this, but the best thing to do is to not create things until you actually need them. This might sound like it adds complication, because it does, but the increase in responsiveness is usually worth it. To find out more about lazy evaluation, take a look at my original article: Improve Your System Performance With Lazy Evaluation.
Cache Everything
Use caching wherever you can. A call to a database is a call across the network, with a possible call into the database itself. The database will be caching everything it can, and so should you; don't retrieve or recalculate data unless you really need to. Store it locally in either a data structure or a third party cache like Redis; your system will run faster and there will be less load on the network.
Caching is something that starts off simple, but can get really complex really quickly. My article System Performance – Part 2 – Cache Assets! also includes a tool to help you decide whether data needs to be cached, and is a good place to get started.
Use Multiprocessing Wherever It Will Be Helpful
There is only so much processing you can squeeze out of a single threaded process, running on a single CPU core. There are those who say that a single thread is all you need, and they're right for some systems. For anything that's doing a lot of processing or input/output, you're going to need something better.
Asynchronous programming is a great way to improve the performance and efficiency of your system, so long as most of the time is spent waiting for I/O tasks like file or network operations to complete. If your system is CPU-bound because it's doing a lot of calculation or other processing, you need to use more processes and/or threads.
Each of the types of multiprocessing have their own advantages and disadvantages, depending on what you're trying to do, and which language you're using to do it. Luckily, there are some great articles about that, for example Concurrency, Multiprocessing, Multi-threading and Asynchronous Code, which is coincidentally written by me! Reading through should give you a good introduction to the type of multiprocessing you need, and the pros and cons associated with it.
Maintain Your Databases
Many developers are scared of databases; they see SQL and all of the associated database theory, and decide that if they MUST use a database, they'll use an ORM to make things easier. On the other hand, some developers see database technologies as an opportunity, and take the time to learn them. That makes them more employable and makes this next step easier: maintain your databases.
When you first design a database, you typically know what you want from it based on the data, but there isn't usually too much data in it. It will normally work quite well, assuming you've implemented the basics of indexes and keys. Once it starts getting bigger, you might find that the performance drops off, slowly at first, but eventually the system might slow dramatically. This isn't unusual; databases have a nasty tendency of showing any mistakes you made or discrepancies between your design and the real world.
Many articles you read at this point will tell you that this is because you need to do a total database redesign. Maybe you do, but unless someone went really wrong you probably don't. Some will tell you that you need to use a NoSQL database instead of a relational database, and they're usually wrong. A few will tell you that you need a bigger database server. This is possible too, but there's a good chance you're just increasing your bills for a temporary improvement.
There are tools that will help you analyze your database while it's in use, and will make recommendations of changes to make to improve performance. These will take your database design from the initial design, generally based mostly on theory, to an optimized version based on the real-world usage of the system. It's best to perform this optimization process whenever you make changes to the database, or when you update the database engine or hardware.
Shift Left
Yes, it's a buzzword, or buzz phrase. In spite of that, it's a useful principle. So, what does it mean? Imagine your software system looks something like this; a lot of them do:
Shifting left means exactly that: shifting processing as far left in the system as possible. For example, database servers are traditionally hulking beasts of systems; plenty of CPUs, lots of memory, numerous disks and caches. Sometimes the database even comprises multiple servers. This all adds up, when they're properly configured and maintained, to make them incredibly fast. The problem with them is they're also "somewhere on the network", with every call to them adding latency and reducing the system's overall performance. Because of this, you need to minimize the number of calls your system makes to the database. Do as much processing as you can within the database and return only the data you need. There are going to be people now who are saying that putting logic in the database is wrong, but that's something for another article.
Any processing you don't do in the database should happen in the business logic. Turning database rows into business objects, processing them according to your business rules and so on.
Web servers and API servers shouldn't be doing much data processing at all. By the time the data reaches this point, the majority of the work done by the server should be serializing the data into a format the clients can handle.
Using this technique will reduce the amount of data being passed around your system, improving the general performance.
Of course, you don't have to do this. You can use a database with few, if any, indexes, haul all of the data out of it and pass it across a wireless network for a JavaScript client to carry out the filtering, joining and processing. I've actually seen this done more than once. I've also seen the problems it causes: the dreadful performance, the high network usage, and the bad feelings and worse language aimed at those responsible.
Summary
Everyone starts their career, as a software engineer or anything else, with little experience. Those who stand out are the ones who work towards advancing their skills and keeping them up to date. These five techniques will help advance your skills and help you produce better software systems. Don't feel that these are the only ones, but they're quite easy to implement and will show you how small improvements to your software can produce disproportionate improvements.