Software-Architecture Lesson 1: Software Architecture Lesson 1: One Login Screen, Three Different An
š Full written solution: https://interview-kit-fe.vercel.app/software-architecture-lesson-01-what-software-architecture-actually-means š» Code on GitHub: https://github.com/timepasshub2539-hue/leetcode-python/tree/main/software-architecture-lesson-01-what-software-architecture-actually-means Chapters: 0:00 The Problem Isn't Python 0:22 Before You Design 0:43 Three Questions, One Login Screen 1:08 System Components 1:22 Four Architecture Questions 1:43 Functional vs Non-Functional Requirements 2:09 Same Code, Different Constraints 2:24 No Perfect Architecture 2:57 Stage One: Simple Architecture 3:08 Stage Two: Handling Traffic 3:14 Stage Three: Database Bottleneck 3:26 Course Roadmap 3:39 Your First Architecture Challenge Lesson 1 of the Software Architecture series: what 'architecture' actually means once a codebase outgrows a single file, and why the line between architecture, design, and implementation matters more than any pattern name. You'll learn the four questions every real system has to answer, why the same login screen gets three different correct answers depending on scale, and why there's no such thing as a perfect architecture ā only one that fits the stage you're at (simple app, traffic shows up, database becomes the bottleneck). Ends with your first architecture challenge to work through before Lesson 2. #softwarearchitecture #systemdesign #softwareengineering #coding #programming Watch next: - Why You Freeze in System Design Interviews (And 10 Habits That Fix It): https://youtu.be/gDbCJzZz34E - Lesson 1: What Is n8n? (Nodes, Triggers & Workflows Explained): https://youtu.be/YSUmiShdHwc - Python Lesson 28: The Phone Number Pattern That Breaks match(): https://youtu.be/5eyIvzetnuw
Introduction
Every developer hits the same wall eventually. You build something small, it works great, and then real users show up. Suddenly the thing that felt "done" starts breaking in ways that have nothing to do with a missing semicolon or a bad variable name. Requests slow down. One new feature quietly breaks another. Adding a simple field to a database table turns into a multi-hour ordeal.
At that point, the problem usually isn't the code. It's the architecture ā or the lack of one.
This matters a lot for interviews too. Coding interviews test whether you can solve a problem correctly. System design interviews test something completely different: can you reason about how a real system holds together under real conditions ā growth, failure, and constraints you don't fully control. Companies ask these questions because production systems don't fail from bad algorithms nearly as often as they fail from bad structural decisions made early and never revisited.
This lesson is the foundation for everything that follows in the series. Before touching Kafka, Kubernetes, or microservices, you need a clear mental model of what architecture even is ā because without that model, every technology you learn later just becomes a buzzword you can name but not actually reason about.
Problem Overview
Let's define the four terms people mix up constantly: requirements, architecture, design, and implementation.
- Requirements describe what you need the system to do.
- Architecture describes how the major pieces of the system fit together.
- Design describes the structure within a component ā classes, interfaces, patterns.
- Implementation is the actual code you write to make it real.
These are four different altitudes of thinking about the same problem, and most developers only ever practice the bottom one: implementation. That's fine for small projects. It stops being fine the moment a system has more than one moving part.
Example
Take a login screen ā something almost every application needs.
- Implementation level: "How do I write the function that checks a password?"
- Design level: "Should this be a service class? A repository? Should I inject the password hasher as a dependency?"
- Architecture level: "Where does authentication actually live? Does every service check credentials itself, or does one dedicated service own identity? How do other services trust that a user is who they say they are? Where do we store tokens? What happens to every other service if the authentication service goes down?"
Same feature. Three completely different sets of questions, and three different people (sometimes the same person, wearing different hats) answering them. This is why "just add a login screen" can mean an afternoon of work or three weeks of infrastructure planning, depending entirely on the scale of the system it's going into.
Intuition
Here's the mental model that makes this click: think about building a house.
Nobody starts a house by laying bricks and figuring out the plumbing as they go. Before a single brick is placed, someone decides where the rooms go, where the doors are, and how water and electricity will actually run through the walls. That decision layer ā the one that happens before implementation ā is architecture.
An experienced engineer doesn't discover the right architecture by memorizing technology names. They discover it by asking questions in a specific order:
- What are we building? ā requirements
- What are the major components? ā architecture
- How do they communicate? ā APIs, events, queues, databases
- What happens when something goes wrong? ā reliability, security, scaling, recovery
Keep those four questions in your head for every system you design going forward. This series will keep coming back to them.
It also helps to split requirements into two categories:
- Functional requirements ā what the system does. A user places an order. A restaurant accepts it. A customer tracks the delivery.
- Non-functional requirements ā how well the system needs to do it. Support 100,000 users. Respond within 200ms. Stay up if a server dies. Keep customer data secure.
Functional requirements tell you what to build. Non-functional requirements are what actually drive architecture decisions ā because a system built for 100 users and a system built for 10 million users can have identical functional requirements and completely different architectures.
Brute Force Solution
If "brute force" has an architecture equivalent, it's this: build everything as one big application, skip the questions above, and add whatever technology sounds impressive.
Idea: Throw in microservices, message queues, and multiple databases from day one because "that's what big companies use."
Algorithm: Guess at scale requirements you don't have yet. Over-provision for problems that don't exist. Add complexity preemptively.
Advantages: None, really ā this isn't a legitimate strategy, it's a trap that looks like foresight.
Disadvantages: Massive operational overhead for a system with no traffic to justify it. Slower development. More failure points. Harder onboarding. You end up debugging distributed systems problems for a product that could have run on a single server.
Time and space complexity: Technically irrelevant here ā the cost isn't computational, it's organizational: wasted engineering hours and unnecessary infrastructure spend.
This is the mistake the lesson explicitly warns against: complexity should only get introduced once the actual problem demands it, not because you know the vocabulary.
Optimal Solution
The optimal approach is to let architecture evolve with real, observed constraints ā not anticipated ones. Here's what that looks like in stages:
Stage 1 ā A single server talking to a database. For a genuinely large number of applications, this is not a starting point to graduate from quickly ā it's just enough, and it stays enough until you have evidence otherwise.
Stage 2 ā Traffic grows. Add a load balancer and a second server, both still talking to the same single database. You've solved the "one server can't handle the load" problem without touching anything else.
| Stage | Bottleneck | Fix Added |
|---|---|---|
| 1 | None yet | Single server + database |
| 2 | Server capacity | Load balancer + second server |
| 3 | Database reads | Cache layer |
Stage 3 ā The database becomes the slow part. A cache goes in front of it to absorb repeated reads. Notice what's still missing: no microservices, no message queue, no sharded databases. Each piece was added because a specific, observed bottleneck demanded it ā not because it sounded advanced.
The trade-offs at each layer matter too:
- SQL vs. NoSQL: SQL gives strong relationships and real transactions; NoSQL gives flexibility and different scaling characteristics.
- Monolith vs. microservices: a monolith is simpler to build and reason about; microservices let pieces scale and deploy independently, at the cost of real operational complexity.
- Cache: speeds up responses, but introduces invalidation and consistency problems you didn't have before.
None of these are universally "right." They're right for the constraints you actually have.
Python Code
This lesson is conceptual, so instead of a coding solution, here's a lightweight way to encode the four architecture questions as a checklist you can run against any system you're about to design:
from dataclasses import dataclass, field
@dataclass
class ArchitectureChecklist:
"""A minimal framework for the four core architecture questions."""
system_name: str
functional_requirements: list[str] = field(default_factory=list)
non_functional_requirements: list[str] = field(default_factory=list)
components: list[str] = field(default_factory=list)
communication: list[str] = field(default_factory=list)
failure_modes: list[str] = field(default_factory=list)
def summary(self) -> str:
return (
f"System: {self.system_name}\n"
f"What we're building: {self.functional_requirements}\n"
f"How well it must perform: {self.non_functional_requirements}\n"
f"Major components: {self.components}\n"
f"How they talk: {self.communication}\n"
f"What happens on failure: {self.failure_modes}\n"
)
def demo() -> None:
checklist = ArchitectureChecklist(
system_name="Food Delivery App",
functional_requirements=["place order", "accept order", "track delivery"],
non_functional_requirements=["100k users", "<200ms response", "stays up on server failure"],
components=["order service", "database", "load balancer"],
communication=["REST API", "database queries"],
failure_modes=["failover to backup server", "retry queue"],
)
output = checklist.summary()
assert "Food Delivery App" in output
assert len(checklist.functional_requirements) == 3
print(output)
if __name__ == "__main__":
demo()
Code Walkthrough
- The
ArchitectureChecklistdataclass exists purely to force you to answer the four questions explicitly instead of jumping straight to implementation. functional_requirementsandnon_functional_requirementsare kept as separate lists because conflating "what it does" with "how well it must do it" is exactly the mistake that leads to premature complexity.componentsandcommunicationmap directly to "what are the major pieces" and "how do they talk to each other."failure_modesforces you to think about reliability before you've written a single line of business logic ā the same way the lesson asks "what happens if the auth service goes down?"demo()runs a concrete example (the food delivery app from the lesson) and asserts the structure holds, so this isn't just a data container ā it's a runnable sanity check.
Dry Run
Using the food delivery app from the lesson:
functional_requirementsgets populated with the three core actions ā placing, accepting, and tracking an order.non_functional_requirementscaptures scale and reliability targets ā 100k users, sub-200ms latency, uptime under server failure.componentslists the order service, database, and load balancer as the major building blocks.communicationrecords that the order service talks to the database via a REST API and queries.failure_modesrecords the plan for when a server dies ā failover and retry.summary()prints all five categories together, giving you a single-page view of the system's architecture before any code gets written.
This is the exercise you should run mentally ā or literally, with this script ā before starting any new system.
Complexity Analysis
Time complexity: O(1) relative to the size of the checklist ā populating and printing five short lists is constant-time bookkeeping, not an algorithmic concern.
Space complexity: O(n) where n is the total number of requirements, components, and failure modes you list ā trivially small in practice.
Why this matters: the "complexity" that actually matters in architecture isn't Big-O ā it's organizational and operational complexity, which this checklist exists to manage by making you enumerate trade-offs before committing to them.
Alternative Solutions
An alternative to a manual checklist is a formal Architecture Decision Record (ADR) ā a short document per major decision capturing the context, the decision, and the consequences. ADRs are heavier-weight than the checklist above but create a searchable history of why a system looks the way it does, which becomes valuable once a team grows beyond one person. For a solo learner working through this series, the lightweight checklist is enough; ADRs become worth adopting once you're working with a team and decisions need to survive turnover.
Edge Cases
- Empty requirements: If you can't fill in functional requirements, you're not ready to think about architecture yet ā go back to requirements gathering.
- Only non-functional requirements, no functional ones: A common trap. Knowing you need "high scale" without knowing what the system actually does leads to over-engineering in the wrong direction.
- Single-component systems: If there's only one component, you likely don't have an architecture problem yet ā Stage 1 (single server, single database) is still the right answer, and that's fine.
- Undefined failure behavior: A system with no answer to "what happens when something breaks" isn't finished, regardless of how clean the happy path looks.
- Requirements that change mid-design: Non-functional requirements especially (scale targets, latency targets) tend to shift ā treat architecture as something that gets revisited, not something decided once and frozen.
Common Mistakes
- Confusing design with architecture. Deciding between a service class and a repository is a design question, not an architecture question ā they operate at different altitudes.
- Adding technology before the problem exists. Reaching for microservices, Kafka, or Kubernetes because they're familiar names, not because a measured bottleneck demands them.
- Skipping non-functional requirements. Building only for "what it does" and ignoring scale, latency, and reliability targets until they cause an outage.
- Treating architecture as a one-time decision. A good architecture at 100 users can be actively wrong at 10 million ā revisiting the decision is part of the job, not a failure of the original decision.
- Assuming there's a "correct" architecture. There's only the right trade-off for your actual constraints ā SQL vs. NoSQL, monolith vs. microservices, cache vs. no cache all depend on context, not universal correctness.
Interview Questions
- "Walk me through how you'd design authentication for a system with three separate services."
- "What's the difference between a functional and a non-functional requirement, and why does that distinction matter for architecture?"
- "When would you choose a monolith over microservices, and why?"
- "How would this system's architecture change if traffic grew 100x overnight?"
- "What happens to your design if the database becomes unavailable?"
Similar Problems
- Design a URL Shortener ā a classic system design problem that forces you to reason about component boundaries and data storage from scratch (and is this series' next challenge).
- Design a Rate Limiter ā tests the "what happens when something goes wrong" question directly, since rate limiters exist purely to protect a system from overload.
- Design a Chat Application ā introduces real-time communication and forces a decision between polling, websockets, and message queues.
- Design a Distributed Cache ā extends the "cache in front of the database" stage from this lesson into a standalone system design problem.
Key Takeaways
- Architecture, design, implementation, and requirements are four distinct altitudes of thinking, not interchangeable terms.
- The four architecture questions ā what are we building, what are the components, how do they communicate, what happens on failure ā apply to every real system.
- Non-functional requirements, not functional ones, are usually what drive real architecture decisions.
- Every architecture decision is a trade-off; there is no universally correct answer, only the right fit for your current stage.
- Complexity should be added only when an actual, observed bottleneck demands it ā not because you know the technology's name.
Watch the Video
This lesson is easier to absorb with the visual walkthrough of the login screen example and the three-stage system evolution. Watch the full video here: {LINK}
About the Series
This article is part of Fun with Learning Technology, a series built for developers who want to actually understand systems, not just memorize pattern names for interviews. Software Architecture is a multi-part course that starts here, with the fundamentals, and builds gradually into APIs, databases, caching, distributed systems, security, the cloud, and full real-world system design ā the same way real systems actually grow.
Call To Action
If this helped clarify what architecture actually means, drop a like on the video on YouTube ā it's the easiest way to support the series. Subscribe on YouTube so you don't miss Lesson 2, and consider subscribing to the Substack newsletter for deeper written breakdowns that go alongside each video. Got a question about your own system's architecture? Drop it in the comments ā and if this was useful, share it with a developer who's about to hit that "it worked with 10 users" wall.
Ready to try it yourself? Solve Architecture problems with instant feedback in the practice sandbox.
Practice now ā


