In 1920, the average years people spent in formal education in the US was 8.5 years. This number increased by 62% in 2020 to 13.8 years. Yet, during the same 100 years, US people’s life expectancy only increased by 41%. If studying is suffering, it’s unfortunate that the increase in our life length failed to match the knowledge inflation.
Back in the 90th, you could build quite an amazing application with assembly language by only dealing with registers and memories. Today, a simple form submission on a web page goes through layers and layers of abstractions. As a competent software engineer, you should have a solid understanding of the entire stack. Isn’t that scary?
There are more than 100 JavaScript frameworks as of today, and a handful of new ones are created each year. If learning one takes a week on average, we’ll be forever stuck in learning mode. When can we ever be ready to handle real jobs?
As software developers, we’re living in a surging time. Concepts, knowledge, skills, tools, best practices — everything’s blowing up. You feel exhausted and desperate with the endless chasing. So it’s time to take a break to think about the meta-learning problem: learning how to learn effectively.
This story is based on the reflection from my 15+ years of continuous learning as a professional software developer and leader, in companies of various sizes, with technologies of different eras, and all the memories of joys and frustration. Learning is quite a personal topic, but I wish it could serve as a valuable reference to you.
1. Turn Passive Learning Into Fantasy Making
One trick I found that helped me gain more during learning is to pretend that I was the creator of the tool/library/framework that I’m trying to master.
First, understand the target problem, and then ask: given my current knowledge of the domain, how would I work out a solution?
Then the learning process feels more like an imaginary dialog with the creator. Instead of memorizing how to use a tool, you engage in a conversation.
Let’s take tRPC as an example:
💡 Problem to solve
Making API calls should feel like regular method calls when developing web apps with TypeScript.Me: Ok, how about analyzing the server-side routines at the build time and generating client-side code to handle the network requests?
Creator: Code generation slows down the developer's iteration cycle. Since with new frameworks, frontend and backend code often reside in the same project; there should be a chance to "cheat" through type sharing between them.
Me: Good idea, but that sounds like quite advanced TS stuff, and I need to strengthen my knowledge over there to see if it's achievable.
⏳ …
Me: I think it should work for the client-side typing part, so auto-completion, etc., will work great in IDE. For the implementation of runtime, since there's no code generation, probably using Proxy could be a clean solution?
Creator: Yes, that’s what I chose to do too.
Me: Cool, client-side needs are solved. How about server-side rendering? Does it need to incur network costs?
…
Of course, you can take another step forward to reach out to the creator and converse for real.
2. Never Skip the Fundamentals When Touching a New Domain
I’ve seen people trying to learn React.js without a solid understanding of JavaScript. It’s simply against physics. Software development today is a vast domain containing many deep silos. When entering a new silo, it’s tempting to start with modern tools designed for development efficiency without having a necessary coverage of the fundamentals.
The problem with this is what’s on the surface (new tools, frameworks, patterns) are more like fashion — they change from season to season. The only constant things are the fundamental “primitives” in the domain. For front-end development, that’s plain HTML/CSS/Javascript. For database development, that’s SQL. For machine learning, that’s linear algebra/calculus/statistics.
I get it that nobody builds UI with vanilla Javascript today; fewer and fewer people write raw SQL in their backend code; models can be trained with a few lines of code. But grasping those primitives makes it easy to adapt to whatever is changing on the surface. Without that, things look chaotic and hard to make sense of.
3. One Tough Real-World Problem Is Worth 1000 Tutorials
The secret of becoming an excellent software engineer is that it’s not the result of gradual daily improvement. Instead, you make a series of small or big leaps leveraging the right opportunities.
Complex real-world problems are such opportunities. They are like mushrooms to Mario and give you a significant boost simultaneously on multiple aspects: domain knowledge, design skills, coding fluency, debugging techniques, confidence, taste, etc. Why? Because those problems are complex for they’re weird, much weirder than leetcode — designing a working solution requires you to grab the essence of the related knowledge. That process is great for internalizing the stuff wandering around in the shallow layers of your brain, and internalization is the key to successful learning of everything.
Tutorials help you implement specific solutions faster, but the tough issues you struggle so hard to resolve to make you a better engineer. Train your eyes to find them in your mundane job, and I bet you’ll find plenty of them.
4. Opportunities in Different Environments: Big vs. Small
Should one start his career within a big company or a startup? It is a very debatable topic. But the reality is that many of us can’t freely choose where to start. You ought to start first, and you can be equally successful wherever you begin. Just need to be conscious of the different characteristics of big and small organizations regarding the learning opportunities they provide.
Big organizations are inevitably bureaucratic, no matter what kind of culture they have. They offer attractive perks — like training courses (often quite expensively procured), HR-organized mentorship, systematic career growth models, etc. Unfortunately, I barely found any of those helpful. What really helped was that, given an established business model and abundant cash flow, companies could afford the luxury of “engineering excellence”. As a developer, you can spend plenty of time iterating your design with your peers or supervisor, having thorough code reviews, sufficient documentation, chasing test coverage rigorously, and refactoring your code before it becomes too ugly. This kind of continuous in-job training makes it a habit for you to think thoroughly before action and always generate quality design and code.
Startups, on the contrary, can’t afford much formality. You’ll be grateful if a simple code review rule is in place. The advantage is that you have lots of freedom to implement things following your own ideas and learn from the consequences directly. The lack of protection from peers, supervisors, and systems makes your work more impactful and its feedback more instant and brutal. You can also fully understand how a system works end-to-end, which is often not possible for larger projects.
Ideally, one should alternate his career between them for more balanced growth. Just be prepared for what you’ll face and try to counterbalance the downside of an environment.
5. What Kind of Mentorship Really Works?
Good mentorship is invaluable to one’s growth, but it’s not easy to encounter one. While this largely depends on how actively you solicit, the candidates you can reach, and luck to a great extent, knowing what kind of mentorship works can also increase the chance of success. I had many opportunities to be a mentee and mentor, and most of them quickly turned into pleasant social connections — which is good and helpful but not what I initially wanted it to be.
A good mentorship should likely possess some of the following traits:
-
The mentor works in a domain that’s closely related to yours
It prevents your conversations from floating at an abstract high level, wasting time for both of you.
-
You can observe how he/she works
People criticize Jack Welch because his books have so many ideas, and some are contradictory. To learn from him, you have to observe what he actually does. Jack didn’t intend to confuse people; it’s just that human thoughts are volatile, and their work is a more faithful reflection of his true talent.
-
You can have in-depth discussions on concrete problems with him/her
Although the goal of mentorship is not to solve specific problems, you only get the opportunity to see how their train of thought differs from yours and get the real sparks you want from mentorship when you have a well-focused and tangible conversation
6. Leverage Corner Opportunities to Expand Your Knowledge Scope
In real jobs, we’re always tied by the status quo. I bet you’ve had frustration that you wanted to try something new at work, but your manager couldn’t afford it. Sometimes the conflict got so intense that people quit their jobs.
But if you can be observant and diligent, you can find corner opportunities to practice new skills without scaring anybody. Here’re some examples from my actual experiences:
- I first learned Python when there was a need to implement an ad-hoc crawler for a project. It later evolved into an actively maintained platform.
- I learned Clojure to implement a plugin for Metabase (the tool my former company used for creating business dashboards). I probably won’t ever use the language anymore in the future, but learning functional programming was fun and eye-opening.
- I built my first machine learning model when trying to identify anomalies from the operational metrics generated by our SaaS product. Still, feeling hard to find such an opportunity? Consider creating or contributing to an OSS project!
7. Participating in the Business Side Fosters Your Technical Vision
Some engineers plan to switch to a business role in the future, and some want to stick to the technical path. No matter which route you choose, getting yourself exposed to business-side stuff is always beneficial, and you should do that as early as possible.
Why? As developers mature, we discover a sad reality: the real difficulty in software engineering is not about choosing which cool tool, using what fancy design, or implementing any sophisticated algorithm/model. Instead, the challenging part is always to support the ever-changing business needs:
The subtle business logic.
The infinite tweaks in the requirements.
The seemingly random moves that your CEO makes.
A good architect, besides being capable of steering a product’s technical directions, is also good at implementing pragmatic solutions for the business and is always preparing for the future of the product. Where does such “vision” come from? By actively participating in business-side activities: checking user feedback, meeting clients, analyzing business results, resolving the pain of sales/marketing teams, etc.
As a result, he always has a vivid image of what the customers look like and how the product is sold, and he always possesses a rough picture of how the product needs to evolve. If you start to practice this as a junior, you’re on the path to becoming the developer loved by the entire company.
You Ought to Love What You Do
This is the last tip; so vital that I have to give it a separate heading. I should put it in reverse in deed: “Ought to do what you love”.
The irony of life is that we always make important decisions too early: choosing a major, picking a career, marrying a person. Sometimes things turn out to be very different from what you expected, and you need to persevere and rediscover your passion before giving up lightly. If, after trying hard and you still feel lost, it’s wise to cut loss in time and look elsewhere. The meta-learning tips are tools that help you make progress faster, but passion is the true foundation of a successful career.
Whichever profession you finally settle into, have fun with the lifetime learning journey!
P.S., We're building ZenStack, a toolkit that supercharges Prisma ORM with a powerful access control layer and unleashes its full potential for full-stack development.