Learning to Learn: How Mental Models Help You Make Sense of Engineering

The pace of change in software development can feel relentless. New frameworks, languages, and platforms appear constantly, and trying to keep up by simply learning more of them can quickly become a losing battle. A more effective strategy is to focus on understanding core principles at a deeper level and using those insights as a checklist for decision-making. These foundational concepts are known as mental models.

This is a way of thinking used by some highly successful people. Richard Feynman, Elon Musk, and Charlie Munger have all described relying on first principles to navigate complex problems. While mental models alone won't guarantee that level of success, they can steer you in the right direction by providing a framework for prioritization and more informed decisions.

To put this into practice, you first need a way to identify and prioritize new concepts worth learning. Then, you need a system for tracking those ideas. The following are some engineering mental models that have proven useful over the years.

Engineering Mental Models

Avoid Silent Failures

When a system component breaks, you need to know about it. Small issues can often point to larger structural problems. Silent failures frequently occur when exceptions are suppressed in code, or when a server goes down without any alert. Using a third-party service to ping critical components can help prevent these situations.

For mature systems, a dashboard that tracks key metrics with automated alerts is essential. The goal is for computers to tell you when something is wrong, rather than discovering it from users or logs. Start measuring and logging everything early on; waiting until a problem arises is too late. To encourage this practice, create helper classes with simple APIs, as things that are easy and obvious are more likely to be used. Once logging is in place, set up automated alerts that post to shared channels and page the on-call developer for emergencies.

Queue Tasks to Manage Load

A system's scalability is often tested by unexpected bursts of incoming requests. The faster it can handle a request, the sooner it can move to the next one. However, in most cases, a system doesn't need to provide a final response immediately—only an acknowledgment that work has begun. In practice, this means placing a task into a background job queue upon request receipt. Queuing also offers a bonus: your system becomes more fault-tolerant, as failed jobs can be retried.

Handling High Read Volumes

Read-heavy systems often read the same data repeatedly, which can strain a database's capacity. The standard solution is to pre-compute this data and store it in a faster location. Instead of letting each request query multiple database tables, you can calculate the expected response in advance and store it in a single, fast data store like Memcached, which houses data in RAM for rapid access.

Handling High Write Volumes

Write-heavy systems are often more challenging than read-heavy ones. Traditional relational databases handle reads well but can struggle with writes, as they spend significant effort on durability and locking, which can lead to timeouts. If a relational database reaches its write capacity, you have a few options. One is sharding, which splits the database into multiple parts to group related data. Another is to move to a NoSQL database, which is optimized for writes. However, this often comes with trade-offs depending on the database type and configuration, which may sacrifice:

  • Atomic transactions
  • Consistency across clusters
  • Durability by not writing to disk immediately

These losses can be mitigated with careful design. For example, creating new rows is generally much cheaper than updating existing ones. Design to avoid multiple flows updating the same row, which can cause lock contention, and instead insert new rows where possible. Despite these challenges, starting with a SQL database and evolving your setup based on specific needs is still a solid recommendation.

Horizontal Scaling as the Long-Term Goal

Vertical scaling means running your software on one large machine; horizontal scaling means running it across many smaller ones. Horizontal scaling is inherently more fault-tolerant, as a single machine failure doesn't cause an outage—its load is simply routed to the others. In practice, all systems that appear “infinitely-scalable” are horizontally scaled under the hood, including cloud object stores, NoSQL databases, and stream processing platforms. The price is increased application and operational complexity, but it's the only way to linearly scale by simply adding more computers.

Prioritize Testability

When choosing between competing solutions, pick the most testable one. If something is hard to test, people will tend to avoid testing it, leaving the system brittle with each future change. Good testability must be baked into the architecture upfront. If your intuition tells you a piece of a system will be difficult to test, pay attention to that.

Antifragility and Learning from Failure

The concept of being antifragile suggests you can grow stronger from shocks, much like a hydra that grows back a stronger head. The software industry has embraced this idea, treating failures as opportunities for improvement rather than just shameful incidents. Netflix’s Chaos Monkey is a well-known example; it intentionally turns off random components to build a more resilient system. When failures do occur, a root cause analysis is conducted in a blameless way. The team starts by discussing what went right before diving into the failure, ensuring the focus is on learning and system improvement rather than assigning blame.

Understand Growth with Big-O

Big-O notation describes the growth in complexity of an algorithm. A practical understanding of the difference between constant, linear, and exponential growth is very valuable. In simple terms, algorithms that perform a single task are better than those that perform many, and those with a fixed number of tasks are far better than ones where the task count grows with each iteration. These principles are also visible at the architectural level.

Margin of Safety

Always leave room for errors or exceptional events. Running a server at 90% capacity might seem cost-effective, but it leaves it vulnerable to traffic spikes. While auto-scaling is a good step, it isn't a perfect solution; an overworked server can cause cascading failures before new capacity comes online. Auto-scaled servers can also have their own issues with disk, connection pools, and other configuration. Expect the unexpected and give your systems breathing room. This principle also applies to release planning—always build buffer time into schedules for unforeseen issues.

Protect the Public API

Changes to a public API should be made with extreme caution. Once something is exposed, it becomes very difficult to change or remove. This means having a clear justification for any alteration and being extremely careful with anything that affects external developers. Mistakes in this area affect a large number of people and are difficult to revert.

Build for Redundancy

Any system with many parts should expect individual failures. This means having backup providers for systems like Memcached or Redis. For permanent data stores like SQL, fail-overs and backups are critical. However, a backup isn't truly a backup unless you run regular drills to confirm you can actually recover the data.

Embrace Loose Coupling

Tight coupling between components creates two significant drawbacks: increased complexity and faster failure propagation. Complex systems are harder to maintain and more prone to error. When systems are loosely coupled, failures are self-contained and can be handled by backups. At the code level, adhering to the single responsibility principle—where each class has one job and communicates through a minimal public API—reduces coupling. At the architecture level, a service-oriented architecture, which divides components by business services and restricts communication to strict APIs, is a key approach for achieving this.

Treat Configuration As Code

Most failures in well-tested systems are the result of configuration changes, not software bugs. Environment variable updates or DNS modifications slip through because they are rarely covered by the same test suites as application code, and the difference between development and production environments makes problems easy to miss. Add tests that exercise different configurations, and keep the dev and prod environments as similar as possible. If something behaves differently between the two, resist the urge to patch the symptom—investigate why the environments diverged.

Prefer Explicit Code

"Explicit is better than implicit," one of the core principles of the Zen of Python, is also critical for readability. Code that relies on the reader having the original author's full context is difficult to understand; an engineer should be able to look at a class and see where each component comes from. In practice, opting for simplicity—having everything in one obvious place—usually beats a convoluted design pattern. Write code for people, not computers.

Practice Thorough Code Review

Code review is one of the strongest levers a developer has for improving both code quality and organizational knowledge transfer. A culture of strong code review changes overall engineering performance. Before shipping, have at least two other developers review your change, and set an expectation that they give complete, thoughtful feedback all at once—multiple rounds of incremental review waste everyone's time.

Because review quality tends to dip with energy levels, review each change with a consistent checklist:

  1. Why is this change being made?
  2. How could this approach or code be wrong?
  3. Do the tests cover this, or do I need to run it locally?

Optimize Perceived Performance

UX research puts the gold standard for loading time at 0.1 seconds (100 ms); slower applications risk losing the user's attention. That responsiveness is hard to hit for non-trivial apps, which is where perceived performance comes in. Show users placeholder content immediately, then swap in the actual content when it finishes loading. The technique makes the product feel fast without requiring every resource to be delivered up front, and it pairs well with the "do minimal upfront work and queue the rest" model.

Validate Input Before Trusting It

The internet works because predictable and secure abstractions sit on top of unpredictable, insecure computer networks. Those abstractions are largely invisible to users, but as an engineer you should always assume the worst about incoming data. Validating user input means checking a handful of fundamental conditions:

  1. The user is who they claim to be (authentication).
  2. The communication channel is secure and no one is eavesdropping (confidentiality).
  3. Incoming data has not been modified in transit (data integrity).
  4. Replay attacks are prevented—the same data cannot be re-sent maliciously.
  5. Malicious data can come from otherwise trusted entities, too.

This is a simplification; more can go wrong, so validation is never optional.

Install Safety Valves

Systems must account not just for worst-case scenarios but also for events that cannot be anticipated. A general safeguard for unknown failures is the ability to stop accepting additional requests while you diagnose the problem. One low-friction approach is an environment variable that toggles the system off or into a safe mode without requiring a new deployment.

Let Caches Expire Automatically

Automatic cache expiration can simplify what would otherwise be fragile manual steps. Consider a server rendering a product page: the cache should invalidate whenever that product changes. The manual method requires two separate actions—changing the product and then expiring the cache. Key-based caching removes the second step. Use a key composed of the product's ID and its last_updated_at_timestamp so that updating the product naturally produces a cache miss and forces a fresh fetch.

The tradeoff is that your cache datastore (for example, Memcached or Redis) accumulates stale entries. Set an expiry time on each cache so old keys clean themselves up, or configure an eviction policy that drops the oldest entries to make room for newer ones.

Gate New Technology on Significant Gains

Technology evaluations are a constant in the industry if a company wants to stay relevant, but each addition comes with costs. New tech fragments the codebase, makes it harder for developers to move between teams, creates knowledge silos, and reduces the libraries and insights that can be shared across the company. The desire to start over and rewrite from scratch often drives these decisions, and nearly always that is a bad idea.

Introducing a new technology is justified in two cases: it enables a previously impossible task, or it makes an existing task roughly 10 times easier. It also makes sense when the current stack's technical limitations are the direct blocker for product goals. Otherwise, the coordination cost outweighs the benefit.

Design for Failure Modes

Product designers focus on the expected path; engineering time, however, goes almost entirely to worst-case scenarios. At scale, every bad thing that can happen will. Asking "What could go wrong?" or "How can I be wrong?" helps counter the human bias toward confirmation and surfaces blind spots. Consider what happens when the system receives no data as well as when it receives a flood—think Min-Max. Plan for occasional hardware failures, slow network requests, and connections that stall completely.

Management Is About Growing People

"Management" may be a misnomer; the real job is growing people. If you align your interests with your reports, a good deal of traditional managing becomes unnecessary. Management is fuzzy and subjective next to engineering, which is why engineers so often struggle with it. It is fundamentally about calibration—approaches that fit you may not fit someone else because the world has different expectations of each person. Books on management offer only the author's own context, so they are best treated as one input, not a manual.

With that calibration caveat, the following principles are worth treating as falsifiable models. They are most useful during weekly planning, and the inverse of each will generally do damage.

Motivate by Aligning Incentives

Incentives drive behavior above anything else; the feeling of procrastinating on tasks you want to avoid is a direct signal of misalignment. Work with your reports to find the intersection of three circles:

  1. What do they want to work on?
  2. What does the product need?
  3. What does the company need?

Venn diagram of the intersection of the three incentives

Energy and momentum come when all three answers overlap: the person is contributing to their product and company while building skills they actually care about. Working in any two of the three zones is also productive. Watch for people whose focus is exclusively on one circle—unilaterally pursing personal interest (ignoring product and company), product demands (ignoring the person a company), or company goals (ignoring the person's needs or product). That may be acceptable temporarily, but it is not a long-term plan. Nudge your people toward overlap.

Create Clarity with a Vision and the "Why"

Having a clear vision for where your product needs to go resolves conflict between tactical options and clears up general confusion. That vision only helps if you communicate it continuously: goal alignment counteracts the broken-telephone effect in communication and the natural entropy of an organization.

Spend Energy Where Leverage Is Highest

This principle is the core of High Output Management. Focus effort on the roughly 20% of tasks that produce 80% of the impact, as the Pareto principle suggests. Without this triage, teams burn hours without accomplishing much. Plan deliberately and direct your energy to the most leveraged and points—and look beyond task lists for leverage.

Several activities consistently pay oversized returns:

Promote Growth Mindset in Your Team

Introducing a new skill is the ultimate renewable resource in a team. Build an environment where reflection and failure are discussed openly; lessons that stick are the ones you learn from mistakes. Cultivate people who obsess over the craft and see failure as information.

Align Your Team on the Common Vision

Shared direction is what points every person's individual leverage at the same targets. It is one of the highest-impact tasks you have.

Build Self-organizing Teams

The only way to scale yourself is to build teams that do not scale through you. Foster ownership, and make a point of offering input without taking authority—suggesting directions without overriding team leaders.

Invest in Communication and Structure

Keep the tools and structures that organize communication healthy. Fragmented communication creates massive waste, so the organizational overhead is often the load-bearing part of delivery.

Get the Architecture Right

Your engineering experience is most valuable here: architecture defines the flow of information in the system, and getting the core components right makes every later layer easier.

Skip Efficiency in Relationship Building

Engineering optimizes for efficiency, but applying that instinct to working relationships usually backfires. One-on-one meetings are a case in point: 30-minute slots are too short. Leave time for banter and free-flowing conversation—it eases people up, produces better exchanges, and surfaces critical information that will never appear in a tight agenda. That does not mean you need endless meetings; on the whole, longer, less frequent sessions beat frequent short ones for this purpose.

The principle applies anytime you want to influence someone or shift their behavior. That process is slow and permanent change does not come from a single conversation. Treat it as a long game.

Hire for Signal, Not Polish

Hiring is one of the highest-leverage activities you can do. When evaluating candidates, look for three signals: smart, gets stuff done, and good to be around.

Beware of the halo effect when assessing intelligence, and be wary of charmers. "Gets stuff done" matters because you don't want smart people who aren't adding value—like investing, aim for people on a great trajectory. "Good to be around" is fraught with personal bias, so keep a simple rule: never hire assholes. Even if they're smart and productive, they'll achieve results at the expense of others and wreak havoc on the team. But do distinguish between assholes and disagreeable people: a disagreeable and useful person is far better than an agreeable but useless one.

Be Useful and Default to Growth

At any moment, you have a choice in how you behave. Pick the option that adds value. There is a useful way to give feedback to a report, and a useful way to review code. Plan your day around being useful to others.

Your default instinct is to seek confirmation bias—to think about how you're right without giving others the same courtesy. Reverse that: ask "how can I make this work" for other people, and "how can this be wrong" for your own ideas.

Don't compete with your reports. As a manager, you want to grow people; be aware of situations where you might be competing and default to being useful over pushing your agenda.

Plan Early, Iterate Fast

Planning gets a bad rap in fast-moving organizations but is critical long-term. Some planning almost always beats no planning. Define a general direction, then iterate toward it. When gathering requirements, ask questions like:

  • What do we want in the near future? Pick the path that gives you the most options for the expected future.
  • How can this be wrong? Counter confirmation bias by explicitly thinking about failure modes.
  • Where do we not want to go? Inversion is useful—it's easier to avoid stupidity than to seek brilliance.
  • What happens once we get there? Seek second-order effects: what will a path unlock or limit?
  • What other paths could we take? Has your team settled on a local rather than a global maximum?

Once you have a direction, you're responsible for communicating it. Getting requirements wrong early sends the team in the wrong direction, which is a net negative.

Build Rapport Before You Build Anything Else

You'll be far more effective at work if you connect with people before diving into tasks. Banter or simply listening—there's a reason people small-talk. Get in the circle before attempting to change it. This pays off across your workflow: Slack threads sound like conversations instead of arguments, and you'll assume positive intent. Alignment in meetings and nudging reports toward positive changes become more natural. Icebreakers and room for silliness help here.

Calibrate Your Approach to People

Management is calibration—you adjust your general approach to each person. An approach that works for one won't work for another. Personality tests like the Enneagram offer good defaults. Type-5 investigators work best with autonomy and new ideas; Type-6 loyalists want frequent support and a sense of being entrusted. If you deliver personal growth, lead with strengths first, then growth areas—strengths give people confidence and momentum, which is also where they add the most value. Once they're running, address areas of improvement with a plan.

Focus on Amplifying, Not Just Fixing

People focus more on negatives than positives. It might be related to deprival super reaction syndrome—we hate losing more than we like winning. Managers often focus on what people do poorly instead of what they do well, so people feel unappreciated. This leads to over-investing in low-performers rather than amplifying high-performers, where an order of magnitude more impact may lie.

Ownership Through Transparency, Not Control

People act like owners when you give them control and transparency. Don't do everything yourself. Talk to people and make them feel included. When people feel left out, they grow anxious about losing control. Be transparent about how decisions are made, and give others autonomy. Expect some mistakes as they calibrate their judgment—nudge in the right direction instead of steamrolling.

There are times when you must act like an owner and lead by example. If you have a nagging feeling about something you don't want to do, take full ownership. Don't ask others to do what you wouldn't.

High Standards Are a Competitive Edge

Tech markets and products are generally winner-take-most—second place isn't viable. Winning brings disproportionate rewards, so aim to be the best in your space and iterate toward it. There's no point in doing things in a mediocre way. High standards push everything forward.

To make it happen, partner with people who expect more from you. Hold your reports to high standards too. The Pygmalion effect is real: people rise to positive expectations.

Accountability Is Non-Negotiable

When things aren't delivered or bad behavior shows up, you must address it. If you don't, that's an implicit message that bad outcomes are acceptable. Failing to hold people accountable can spiral.

Calibrate your approach to your work style and situation. Enforce all deal-breaker rules. Set clear expectations early. When something goes wrong, work with the other person or team to understand why—was it a technical issue, tooling, or a leadership problem?

Bring Others Up

Great and ambitious people raise the bar for everyone. Avoid self-obsessed growth seekers. As a manager, your job is to bring other people up. Don't take credit for work you didn't do; give recognition to those who did. Most people care about their own growth, so being the person who genuinely spends time on others' development sets you apart and makes more people want to work with you.

Stress Management as a Skill

Culture comes from the top and bottom—people mimic those in authority. Be mindful of your actions. As fatigue builds, awareness drops; watch your energy entering meetings. Energy and positivity are something you can give away, and it costs you nothing.

Managing stress matters too. Most people cope with problems that have clear yes/no answers, but tricky nuance, unclear paths, or split decisions tend to bubble up. Ambiguity and conflicting signals stress many people. Treating stress management like a skill matters. Adding emotion to stressful situations doesn't help—keep your cool.

Lead With Long Horizons

Engineers typically operate on the order of days to weeks. As you expand influence, you must stretch your time horizon. A manager's horizon is longer than an engineer's but shorter than someone focused on high-level strategy. Project your team and reports a few months into the future and anticipate their challenges. This lets you resolve issues before they occur— and helps you be proactive instead of reactive.

Give Feedback to Those You Care About

Would you criticize a stranger for something harmful? Probably not. If you knew them, you'd reason and nudge. Feedback is for people you care about and want long-term relationships with, including at work. Even if someone isn't your report, share feedback if you can deliver it usefully.

People get defensive, so build rapport first. Once someone believes you're on their side, they're more receptive: get in the circle. While code review feedback is best delivered all at once, that's not true for one-on-ones. Many default to quick feedback, but it doesn't work well with people you lack rapport with and only works clearly from a position of authority. The shortest path is often not the path of least resistance—build rapport before getting to work.

Why Foundation Matters

Most of us gravitate toward the constant churn of new technologies. The real leverage, though, comes from distinguishing what changes from what doesn’t. The foundations of computing and information theory — the ideas that have been stable for decades — begin to feel like a superpower when you internalize them.

Understanding the fundamental constraints gives you the ability to recognize when something is a novelty and when something is real. When you encounter a new language construct you get better at pattern matching: "Is this really doing something new, or is it doing something I already understand—just with different vocabulary?"

Knowing foundational concepts is helpful in the abstract with overall problem solving and decision making, but they become a superpower when one is armed with certain skills. The foundational topics you are truly comfortable navigating enable efficient, pragmatic decision-making.

Your mental map of "stable concepts" mirrors how we build software. When you depend on another piece of code, there’s a risk that engineering or the ecosystem might change that code underneath you. Just as an "immutable core" removes this risk by making stable, unchanging code the subject to build your whole app against, if you connect your decisions to stable concepts that don’t change, you free yourself to focus on parts that are actually changing. When you notice this theme repeating across virtually all fields — this is how software has addressed that fundamental requirement of immutability — you stop repeating history and start actively building on principles.

Too often, we have blind trust in our languages’ implementations and the technology that they provide. But sometimes breaking those programming rules to understand what really happens gets you right to the intersection where testing, architecture, product development, and foundational background knowledge, like knowledge of mathematics all converge. This enables crossing the bridge from knowing about, to internalizing.

A design pattern captures reusable knowledge and abstractions that we can use to capture good ideas, but software patterns sometimes betray the very essential. There’s a difference between knowing a pattern exists and recognizing why it exists. Once you deeply internalize the mechanics of why you choose a fundamental principle as “the subject” rather than approaching a problem from the state of fixing, the need for certain patterns just evaporates. This, in turn, provides a common strategy for thinking — a mental model you can use to problem-solve situations that might look completely different at the surface.

Distinguishing the "what" from the "how"

Skill comes with understanding the distinction between "what" solutions to use and "how" solutions behave. A useful way to split software is: solutions that are What, and solutions that are How. The what are programming languages, patterns, goals and constraints of what you’re using and fixing — these are the platform. The how are solutions to those problems — and these face the churn and evolve continuously.

Once you can separate these, you recognize where problem space lies. For example, the event loop is a stable, foundational piece at the heart of virtually everything — how browsers, graphics, async handling and even operating system all work. It works through a mechanism:

Building Mental Models of Ideas That Don’t Change

Everything on the main thread runs, and the "microtasks" get the desired priority over everything else, ensuring macrotasks like events are left to handle once the stack runs out. State and environment are saved and restored when events occur, and the browser continues from where it left off. The entire strategy of responsive user interface, servers handling thousands of open requests and, most importantly, the many important engines (such as Ruby’s or JavaScript’s runtimes) can all be boiled down to this idea.

Marking views or data as "dirty" and deferring the actual processing until later (often called a dirty flag or a transaction log) is another foundational pattern. Whether it’s a UI framework scheduling a batch of DOM/Document updates, an operating system writing metadata to a log, or a program buffering output before actually doing the same, this is eventually-consistent logic that enables massive improvements in performance. After you really learn to spot these patterns, every system you’ve interacted with becomes much better understood.

If you can master a small number of highly general-purpose mental tools — zero-sum, counting, probability, calculus — you will have an easier time making sense of a wide variety of problems. Formalizing an idea in mathematical language (or validating it internally) forces you to acknowledge edge cases and conflicting constraints. Most engineering fear is actually founded on oversights with constraints that you’re unknowingly bypassing. Mathematical — or for engineers, "rigorous" — thinking introduces an extra, intentional step in problem solving that makes you break down "what’s the same" and "what’s actually different here" — a very natural way to build new neural pathways of knowledge.

Invest in You

One way to invest in this way of thinking is to switch your technology stack altogether. It’s a great move for growth because rather than taking your current problems from the field of an already known industry and "porting" them across a language in the same domain, a total change forces you to learn a new way to think about problems. In an economy that demands skills, one of the simplest ways to make a massive dent is to aggressively invest in yourself.

Another powerful way to get quickly into problem spaces where fundamentals matter is to write code generators and compilers. Even just transcribing problems with algorithms that already exist — writing code that generates and munges code — enables you to discover intuition that off-the-shelf solutions often hide.

There is an adage: "Advice is what we ask for when we already know the answer." Solving problems with foundations, patterns and stable ideas acts in that way — it teaches you that deep insight and real growth come from spending time in problem spaces rather than simply moving to novel, changing ideas. Building with the "constant" part of the field gives a compounding, ongoing reward: our mental models keep us anchored.