Learning to Think Like an Engineer: The Day I Stopped Just Writing Code

TL;DR

“Coding is execution. Engineering is understanding.”
I didn’t realise the difference — until I paused the build to map the architecture.

Why I Had to Stop Before I Even Started

I sat down to begin building the first slice of my new project — a simple LLM app for parents — and... roadblock time 😬

It wasn’t a motivation issue. I had a good sense of what the app should do and a rough stack in mind: Next.js, Drizzle, OpenAI, Zod, OAuth. But something felt off.

The problem, I realised, wasn’t that I didn’t know how to code — it was that I didn’t know how it all fit together. I couldn’t see the shape of the system I was trying to build. And without that, I didn’t know how to start, or how to be confident in my stack and what I was building.

So I paused.

Before jumping into code, I decided I needed to understand the architecture — the flow, the boundaries, the responsibilities. I didn’t want to just write code. I wanted to understand how the system worked.

This post is a reflection on what I did, what I learned, and why this was one of the most important early steps I’ve taken in learning to think like a full-stack engineer.

Why I Paused: I Had(ish) the Tech Stack — But Not the System

I’d already chosen my stack. I had a rough idea of the libraries I planned to use. But I didn’t fully understand how they fit together — or what each one was responsible for.

I couldn’t break my work down into slices see my post on Minimum Viable Slice because I couldn’t tell where one concern ended and another began. Was this part of the UI? The backend? Should I validate input in the handler or the service? I didn’t know — and it made every step feel uncertain.

It felt like having pieces of a jigsaw without the picture on the front of the box to guide me.

Pausing to think about the system helped me:

  • Clarify what was actually in my tech stack — and what purpose each piece served

  • Understand how tools like Zod, JWT, and Drizzle mapped onto architectural concerns like validation, auth, and persistence

  • Iterate more confidently on my scope and design — because I now had somewhere to put things

What I Did: Creating a Mental Model of My Application

The first thing I did was map out the major concerns: UI, API, validation, authentication, business logic, persistence.

Then I walked through a single flow: a user submits a prompt. What happens from there?

I traced the data as it moved from the frontend, through the route handler, into validation, through services, to the database, and back.

This is how I thought about concerns, components, and responsibilities:

  • What are the main concerns (broad areas of responsibility)?

  • What are the main components (things that fulfil those concerns)?

  • What are their responsibilities (the job they do)?

Here's what I came up with:

ConcernComponentResponsibility
Presentation/UIFrontend (Next.js)Handle routing, form input, display responses & history
Business Logic/APIBackend API Routes (Next.js)Validate input, handle auth, call OpenAI, access DB
PersistencePostgreSQL + Drizzle ORMStore user data, prompt/response history
AI IntegrationOpenAI APIGenerate responses from prompt
AuthenticationGoogle OAuth (via Auth.js)Login flow, user identity verification
Session/AuthJWT LayerIssue/verify user tokens for protected routes
SecurityRate Limiting & Input ValidationPrevent abuse, ensure request safety
Environment/Secrets.env ConfigManage API keys and sensitive settings
DeploymentDocker & DigitalOceanContainerized deployable app instance
CI/CDGitHub Actions (optional)Automate test & deploy pipeline (optional for portfolio)

This helped me understand not just what tools I had, but why each one existed and what part of the system it served.

By following the flow through the system, I started to see that architecture wasn’t abstract or static — it was dynamic and revealing. I began to notice gaps, assumptions, and flaws in my knowledge.

system architecture diagram

This wasn’t just an academic exercise. It helped me understand:

  • What a “concern” really is, and how to separate them

  • The difference between a domain (like "auth") and a component (like AuthForm.tsx)

  • What boundaries exist in my system — and why those boundaries matter

Up until then, architecture felt like a checkbox — something you’re told to sketch out at the start of a project, but not something grounded in understanding or learning. Then I came across Martin Fowler’s definition of architecture as a 'shared understanding of the system’s design' — and things clicked. In this case, the shared understanding was just between me and myself — but that alone was transformative.

“Architecture is the shared understanding of the system's design.” — Martin Fowler

What I Learned: From Vague Tools to Clear Responsibilities

Once I saw how the system worked, I started to understand where each tool fit — and why it was there.

For example:

  • Zod: a runtime validator that ensures incoming data (like API requests) matches the expected shape. While TypeScript interfaces define static types at compile time, Zod performs validation at runtime — so your app can safely use the data it receives.

  • TypeScript interfaces: static contracts that describe the expected shape of data. They help during development but aren't enforced at runtime.

  • JWT: a signed token (not middleware itself) that acts like a passport — proving the user's identity and authorisation. It's typically verified by auth middleware, which allows or blocks access to protected routes and services.

  • Drizzle ORM: a type-safe way to define your database schema and query your DB in TypeScript — removing the need for raw SQL in most cases.

I stopped seeing tools as magic boxes. I started seeing them as components in a larger system — each with a role and a place.

This also exposed gaps. For example, I realised I didn’t fully understand how OAuth and JWT worked together. But now that I knew where in the flow that gap lived, I could go learn it in context.

And that’s the difference. I wasn’t just Googling things — I was learning how they fit.

Why It Helped: I Can Now Approach the Build with more Confidence

I haven’t written the core feature code yet — but I now know what I’m building, how I can approach the design, and how to break it down into smaller, logical parts using my Minimum Viable Slice plan.

The architecture will evolve — and that’s fine. But now:

  • I understand where different types of logic belong

  • I know what my first slice should include

  • I can scope, plan, and test without guessing

  • And when something breaks later, I’ll have a better idea of where in the system to look

This isn’t about perfection. It’s about moving from fog to flow — slowly, but intentionally.

Is This Overthinking or Useful?

Honestly, I worried I might be overthinking it. Shouldn’t I just be building things and figuring it out as I go?

But over time, I’ve come to see that this is part of building. Designing isn’t procrastination — it’s what gives the build direction.

Having a rough architectural map gave me clarity, helped me learn more deeply, and will save me time and confusion later. Especially as a junior developer, this kind of deliberate thinking can be a force multiplier.

Even now, I may still get stuck — but I’ll be stuck in a known place, not lost in a maze.

Advice to Other Juniors: Map Before You Build

If you're starting a project and feel stuck, try stepping back.

Ask yourself:

  • How does a request move through your system?

  • What part handles validation?

  • What’s responsible for business logic?

  • Where should this data end up?

Drawing a rough architecture diagram, walking through a single flow, and identifying where each tool or function lives can be a huge unlock.

These diagrams don’t need to be perfect or pretty — they just need to help you reason through what you're building.

Still not sure whether your planning is helpful or just a detour? Here’s a quick test I found useful:

QuestionIf yes…If no…
1. Does it help clarify your system?✅ Keep it⚠️ Too abstract
2. Does it help break things into shippable slices?✅ Useful⚠️ Planning black hole
3. Is it scoped to current work (e.g. MVS1)?✅ Lean⚠️ Premature abstraction
4. Is it helping you learn and make decisions?✅ Productive⚠️ Risk of procrastination

This helped me validate that I wasn’t just stalling — I was preparing to build well.

You don’t need to be an architect to think architecturally. You just need to care how your system fits together.

Final Thoughts

This reflection doesn’t mean I’ve figured it all out — far from it.

But it was the first time I stopped just writing code, and started thinking like an engineer.
Building moved beyond just code.

Looking ahead, as AI becomes more integrated into our day-to-day tools, I think we’ll need to be less like coders, and more like engineers — even at a junior level.

And right now, using system design and architecture to uncover gaps in my knowledge and build with more intent feels like the right next step.

For me, that shift made all the difference.

What’s Next?

I’ll use this blog to share what I’m learning, document side projects, reflect on my journey into software engineering and to share my thoughts on the industry.

You can sign up for email updates here, follow my GitHub, or connect with me on Linkedin as I build stuff 🙂


Dileepa Ranawake is a full-stack software engineer, accessibility advocate, and former startup co-founder. He’s exploring how AI, accessibility, and engineering can come together to build tech-for-good.

0
Subscribe to my newsletter

Read articles from Dileepa Ranawake directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dileepa Ranawake
Dileepa Ranawake

Hey, I'm Dileepa 👋🏽 🙋🏽‍♂️ About Me I'm a UK-based full-stack engineer 🇬🇧 I co-founded a startup that uses AI to make email accessible for Dyslexia & ADHD 🚀 Previously ran a health tech company, am a TEDx speaker and spoke at Warwick University on AI and Accessibility 🎤 Nasa Hackathon Nominee 👾 I'm a neurodiversity advocate and love building products that make the world a better place ❤️ I’m interested in how AI is changing the user-interface — particularly how it can make tools more accessible, enhance learning, and support mental health 👨🏽‍🏫 On this blog, I write about: Industry thoughts My (unvarnished) journey learning full-stack development. AI, tech for good. I believe in thoughtful engineering, inclusive design, and building products that benefit humanity. 🚀 If you're into accessible products, ethical AI, tech for good, or learning out loud — let’s connect. Drop me a follow here, connect with me on Linkedin check out my GitHub or send me an email on dileeparanawake@icloud.com PS. To see my latest posts don't forget to subscribe to my Hashnode newsletter too 🙂