Software Design 101

Author: Htunn Thu Thu

Last Updated: January 4, 2026

Tags: #SoftwareDesign #TypeScript #BestPractices #DesignPatterns

Introduction

Software design is the blueprint that transforms ideas into maintainable, scalable applications. Throughout my journey building various personal projects β€” from AI-powered intelligent agents to IoT monitoring platforms and MCP servers β€” I've learned that good design isn't about following rules blindly. It's about making informed decisions that balance simplicity, maintainability, and functionality.

This guide shares my personal experiences and learnings about software design fundamentals, using TypeScript for all code examples. Every pattern, principle, and practice discussed here comes from real challenges I've encountered and solutions I've implemented in my own projects.

Why Software Design Matters

Early in my development journey, I built systems that "worked" but were painful to maintain. Here's what I learned the hard way:

Problems I Encountered

πŸ”΄ Scope Creep: Without clear design, my projects grew in unpredictable directions. What started as a simple port checker evolved into a complex security suite because I didn't plan the architecture upfront.

πŸ”΄ Technical Debt: When I rushed to "just make it work" in my IoT monitoring platform, I accumulated debt that took weeks to refactor later. Functions that did too many things, unclear naming, and tight coupling made every change risky.

πŸ”΄ Difficult Collaboration: Even working on my own projects, returning after a few weeks felt like reading someone else's code. Without proper design documentation, I wasted hours reunderstanding my own decisions.

Benefits of Good Design

βœ… Faster Development: After applying design principles to my MCP server project, adding new security tools became a matter of minutes rather than hours. The plugin architecture I designed made extensions trivial.

βœ… Easier Debugging: When my LLM agent had issues, the modular design with clear separation of concerns helped me isolate problems quickly. Each component had a single responsibility, making testing straightforward.

βœ… Reusable Components: I've reused my authentication middleware, error handling patterns, and configuration management across multiple projects because I designed them to be modular and independent.

The Software Design Process

Based on my personal development workflow, here's the process I follow for every project:

1. Problem Statement

I start by clearly defining what I'm trying to solve. For my SimplePortChecker MCP project:

"Build an MCP server that brings network security assessment tools directly into GitHub Copilot, enabling developers to perform security checks without leaving their IDE."

This simple statement guided every design decision that followed.

2. Define Scope

I list what's included and excluded to prevent scope creep:

Includes:

  • Port scanning capabilities

  • DNS lookups

  • SSL/TLS certificate validation

  • Integration with VS Code Copilot

Excludes:

  • Full-featured vulnerability scanning

  • Automated penetration testing

  • Network mapping features

Assumptions:

  • Users have basic networking knowledge

  • Running in development environments, not production networks

  • Copilot is already configured

3. Identify Use Cases

I map out how users will interact with my system:

  • Developer wants to check if a port is open

  • Developer needs SSL certificate details for a domain

  • Developer wants DNS information for troubleshooting

  • Developer needs batch port scanning results

4. Define Requirements

Based on use cases, I specify exact behaviors:

  • The server SHALL provide port scanning for specified host and port

  • The server SHALL validate SSL certificates and return expiry dates

  • The server SHALL handle errors gracefully when hosts are unreachable

  • The server SHALL return results in under 5 seconds for single checks

5. Design Architecture

I create a high-level structure before writing code. For my IoT platform:

6. Implement & Document

I write code following the design, documenting decisions as I go. My README files explain not just what the code does, but why I made certain design choices.

What You'll Learn

This guide is organized into practical topics, each illustrated with real TypeScript code from my projects:

Core Principles

Design Patterns

Practical Design

My Tech Stack

All examples in this guide use:

  • TypeScript (ES2020+) - For type-safe, maintainable code

  • Node.js - Runtime environment

  • Express/FastAPI - API frameworks (showing both when relevant)

  • Docker - For containerized deployments

  • VS Code - Development environment

Key Projects Referenced

Throughout this guide, I reference actual code from these personal projects:

  1. Agentic LLM Search - Intelligent agent combining local LLMs with Azure OpenAI

  2. IoT Monitoring Platform - LLM-powered observability for sensor data

  3. SimplePortChecker MCP - Network security tools for GitHub Copilot

  4. AI Chatbot for Multi-tenant POS - Microservices architecture for retail systems

How to Use This Guide

For Beginners

Start with the fundamentals:

  1. Study patterns as needed for your projects

For Intermediate Developers

Focus on patterns and advanced topics:

  1. Jump to specific design patterns you're interested in

  2. Review Component Design for architectural guidance

  3. Study Best Practicesarrow-up-right to level up your code

For Code Reviews

Use this as a reference:

Personal Learning Philosophy

My approach to software design is based on three principles:

1. Learn by Doing

Reading about patterns is useful, but I only truly understood them by implementing them in real projects. Every example here comes from code I actually wrote and refactored.

2. Simple First, Optimize Later

I used to over-engineer solutions. Now I start with the simplest design that works, then refactor when complexity is justified. YAGNI (You Aren't Gonna Need It) has saved me countless hours.

3. Document Decisions

I maintain design decision records (ADRs) in my projects. When I chose to use the Strategy pattern for my LLM model selection, I documented why. Months later, this saved me from second-guessing myself.

Common Misconceptions

Through my journey, I've encountered these myths:

❌ "Design patterns are only for large teams" - I use patterns in solo projects. They make my code easier for future-me to understand.

❌ "Good design takes too much time" - Poor design takes more time in the long run. The hours I spend designing upfront save days of debugging later.

❌ "TypeScript adds unnecessary complexity" - Type safety has caught countless bugs before runtime. It's extra effort upfront but prevents production issues.

❌ "You need to know all patterns" - I regularly use only about 5-7 patterns. Knowing when NOT to use a pattern is equally important.

Getting Started

Ready to dive in? Here's your path:

Start with SOLID Principles β†’


Additional Resources

From This GitBook

External Resources

My GitHub Repositories

Links to actual implementations of these patterns in my projects (check individual pattern pages for specific repos)


Feedback & Contributions

This is a living document based on my continuous learning. As I build more projects and learn new patterns, I'll update this content. If you spot any issues or have suggestions, feel free to reach out!

Next: SOLID Principles with TypeScript β†’

Last updated