paulserban.eu

Writing Edition

Paul Serban

Writing, snippets & book notes

January 28, 2025

From Code to Consensus: Building Confidence as a Lead Fullstack Engineer on MERN and AWS

How senior engineers grow from strong individual contributors into architects who can lead technical discussions and align cross-functional teams

Introduction

Most engineers reach the "lead" title because they write good code, ship features reliably, and understand a system deeply. What the title rarely comes with is a manual for the part of the job that actually determines whether a team succeeds: standing in front of a room - physical or virtual - and steering a group of stakeholders with different incentives toward a technical decision everyone can live with. For engineers working across the MERN stack (MongoDB, Express, React, Node.js) and AWS, this is a particularly wide gap, because the stack itself spans so many concerns: data modeling, API design, client rendering strategy, and cloud infrastructure. A lead is expected to have an opinion on all of it, and to be able to defend that opinion in a conversation with product managers, security engineers, and other tech leads who may not share the same context.

This article is not about becoming a better coder. It assumes you already are one. It is about the specific, learnable skill of leading architectural discussions and driving solutions cross-functionally - a skill that looks like confidence from the outside but is, on the inside, mostly structure: knowing what questions to ask, how to frame trade-offs, how to document decisions so they survive contact with reality, and how to disagree productively with people who report to different managers than you do. We will ground the discussion in the MERN and AWS context because the concrete details matter - a generic "how to lead meetings" post will not tell you how to reason about DynamoDB partition keys versus a MongoDB Atlas cluster, or when a monolithic Express API should be split into Lambda functions. Where useful, we will use realistic code and configuration examples, not toy snippets.

Context: Why This Skill Gap Exists

There is a well-documented pattern in engineering careers, discussed at length in Camille Fournier's The Manager's Path and Will Larson's Staff Engineer: Leadership Beyond the Management Track, in which technical growth and influence growth decouple somewhere around the senior-to-lead transition. Up to that point, your job is mostly to solve problems that are already scoped. As a lead, you are increasingly handed problems that are not scoped at all - "customers are complaining about page load times," "we need to support a new market's compliance requirements," "the mobile team wants real-time updates" - and your first job is not to write code, it is to figure out what the actual problem is and who else needs to be in the room.

This is uncomfortable for a specific reason: engineers are trained to be right, and architectural discussions reward being useful more than being right. A technically correct answer that arrives after the decision has been made, or that ignores a hard constraint from the security or data team, is worse than a slightly imperfect answer that the room can actually act on. Many strong engineers stall at this transition because they treat every architecture conversation like a code review - looking for the single correct answer - when it is closer to a negotiation among constraints, some of which are technical (AWS service limits, MongoDB's document size cap of 16MB, Node.js event loop behavior under load) and some of which are organizational (a team's on-call capacity, a compliance deadline, a budget ceiling).

The MERN-on-AWS context sharpens this further. A "simple" decision like choosing between AWS Lambda and a long-running Express server on ECS or EC2 has second-order effects on cold-start latency, connection pooling to MongoDB, cost model, and observability tooling. A lead who has only ever operated inside their own service boundary will default to whatever pattern they know best, which is a natural but limiting instinct. Building comfort with architectural discussion means building comfort with saying "I don't know yet, here's how we'll find out" in front of people who expect you to have the answer - and doing so without losing credibility.

The Anatomy of a Good Architectural Discussion

A useful mental model borrowed from distributed systems literature and popularized in software architecture practice is that every architectural decision is really a trade-off among a small number of axes: consistency versus availability, latency versus cost, flexibility versus operational simplicity, and speed of delivery versus long-term maintainability. Leading a discussion well means naming these axes explicitly before the room starts arguing about which specific technology to use. If a team is debating whether to use MongoDB change streams versus polling for a notification feature, the real conversation is about latency requirements and infrastructure cost, not about which tool is "better" in the abstract. Naming the axis turns a values disagreement into a data question, which is far easier to resolve.

The second structural piece is separating the discussion into distinct phases: problem framing, options generation, evaluation against constraints, and decision recording. Google's engineering practices documentation and the broader RFC (request for comments) tradition used at companies like Rust's core team and many large tech organizations converge on the same insight - a written proposal that precedes the meeting does most of the persuasive work, because it lets people react to specifics instead of debating vague preferences live. As a lead, your highest-leverage action before a big architecture meeting is often not more research; it is writing a one-to-two-page document that states the problem, two or three real options with their trade-offs, and a recommendation, and circulating it 24-48 hours in advance.

Practical Implementation: Turning Discussions into Artifacts

Concretely, this means adopting Architecture Decision Records (ADRs), a lightweight format popularized by Michael Nygard in 2011 and now widely used across the industry. An ADR is a short markdown document capturing a decision, its context, and its consequences, checked into the repository next to the code it affects. For a MERN/AWS team, a typical ADR directory might live at docs/adr/ and look like this:

# ADR-014: Move notification delivery from polling to MongoDB Change Streams + SQS

## Status
Accepted

## Context
The notifications service currently polls MongoDB every 5 seconds from an
Express endpoint to detect new events. This costs ~$400/month in read
capacity units and adds up to 5s of latency, which product has flagged as
a UX problem for the new "live activity" feature.

## Decision
We will use MongoDB Change Streams (backed by the replica set oplog) to
detect inserts on the `events` collection, publish matching events to an
SQS queue, and have a Lambda consumer fan out to connected clients via
API Gateway WebSockets.

## Consequences
- Positive: sub-second delivery latency, no polling cost.
- Negative: Change Streams require a replica set (already true for us) and
  add operational dependency on a long-running Node.js listener process
  (will run on a small Fargate task, not Lambda, since Change Stream
  cursors are long-lived connections).
- Follow-up: need a dead-letter queue and alerting for cursor resumption
  failures (resume tokens expire after the oplog window, currently 24h).

Writing this before the meeting forces you to confront the resume-token failure mode early rather than discovering it in production. It also gives non-engineering stakeholders - a product manager, say - something concrete to react to: "sub-second delivery" and "$400/month" are numbers they can weigh against a roadmap, whereas "change streams vs. polling" is not.

On the AWS side, the same discipline applies to infrastructure decisions, and it pairs well with infrastructure-as-code so the discussion has a concrete artifact to point at rather than a diagram that drifts from reality. A minimal AWS CDK stack (TypeScript) illustrating the Lambda/SQS piece from the ADR above:

import { Stack, StackProps, Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';

export class NotificationFanoutStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const deadLetterQueue = new sqs.Queue(this, 'NotificationsDLQ', {
      retentionPeriod: Duration.days(14),
    });

    const eventsQueue = new sqs.Queue(this, 'NotificationsQueue', {
      visibilityTimeout: Duration.seconds(30),
      deadLetterQueue: { queue: deadLetterQueue, maxReceiveCount: 3 },
    });

    const fanoutFn = new lambda.NodejsFunction(this, 'FanoutHandler', {
      entry: 'src/handlers/fanout.ts',
      memorySize: 256,
      timeout: Duration.seconds(10),
      environment: {
        API_GATEWAY_ENDPOINT: process.env.WS_API_ENDPOINT ?? '',
      },
    });

    fanoutFn.addEventSource(new SqsEventSource(eventsQueue, {
      batchSize: 10,
      reportBatchItemFailures: true,
    }));
  }
}

This stack is deliberately small enough to fit in a single review, which matters cross-functionally: when a security engineer or an SRE asks "what does this actually deploy," you can point at forty lines of TypeScript instead of a slide. That concreteness is often what separates a lead who is trusted with ambiguous problems from one who is not - stakeholders learn that your proposals resolve into things they can inspect, test, and roll back.

Trade-offs and Pitfalls in Cross-Functional Architecture Work

The most common failure mode is over-indexing on technical elegance at the expense of organizational reality. A lead might correctly identify that migrating from MongoDB to a purpose-built time-series database would improve query performance for an analytics feature, but if the data team has no bandwidth to support a second database technology for the next two quarters, pushing the "correct" answer anyway will burn trust rather than build it. Good architectural leadership includes the discipline of presenting the technically superior option alongside a pragmatic fallback, and being genuinely willing to recommend the fallback when organizational constraints make it the better real-world choice. This is different from capitulating on quality; it is recognizing that a system's health depends on the team's ability to operate it, not just on the diagram's elegance.

A second pitfall is running discussions that are technically rigorous but exclude non-engineers from meaningful participation. If a product manager or a designer sits through forty minutes of debate about Node.js worker thread pools and never gets to weigh in, two bad things happen: the decision loses input on user-facing consequences, and the non-engineers stop showing up to future discussions, which erodes the cross-functional alignment you were trying to build in the first place. The fix is structural - explicitly reserve time in the agenda for "what does this mean for the roadmap/design/support burden," phrased in terms of outcomes rather than mechanisms. It is also worth watching for the reverse failure: engineers who defer entirely to non-technical stakeholders on questions that are genuinely technical, out of a desire to avoid conflict. Neither extreme produces good decisions.

Best Practices for Leading Architecture Discussions

Prepare a written proposal before every consequential meeting, even a rough one. The AWS Well-Architected Framework's own review process is built around structured questions per pillar (operational excellence, security, reliability, performance efficiency, cost optimization, sustainability), and borrowing that structure - asking explicitly "what does this cost," "what happens when this fails," "who is paged when this breaks" - keeps a MERN/AWS discussion from drifting into a pure technology-preference debate. You do not need to run a full Well-Architected review for every feature, but reusing its pillar structure as a checklist for your own proposals is a low-cost way to catch gaps before a stakeholder does.

Second, practice explicitly separating "decisions" from "explorations" in how you communicate. When you open a meeting by saying "I want us to leave with a decision on X" versus "I want to lay out the options on Y before we commit," you set expectations that prevent the frustrating experience of a meeting that everyone thought was decisive turning out to be exploratory, or vice versa. This single habit - stating the meeting's decision status up front - resolves a surprising fraction of cross-functional friction, because much of that friction comes from mismatched expectations about what a conversation was for, not from actual disagreement about the technology.

Third, invest in your own technical range deliberately, not reactively. A lead on a MERN/AWS stack benefits from working knowledge that extends slightly past the boundary of daily coding: enough AWS IAM and networking to reason about a security review, enough MongoDB internals (indexing strategy, the aggregation pipeline, sharding behavior) to evaluate a data modeling proposal without deferring entirely to a DBA, and enough familiarity with React's rendering model to weigh in on a frontend performance discussion. You do not need to be the expert in the room on every axis - you need enough fluency to ask a good question and correctly weigh the expert's answer.

Analogies and Mental Models

One useful mental model is to think of yourself as a translator rather than a judge in cross-functional architecture discussions. A judge's job is to determine who is right. A translator's job is to make sure the security engineer's concern about IAM scope, the product manager's concern about the March deadline, and the frontend engineer's concern about bundle size are all expressed in a shared vocabulary - usually cost, risk, and time - so the group can actually compare them. Leads who see themselves as judges tend to alienate stakeholders whose expertise lies outside code; leads who see themselves as translators build the kind of trust that gets them looped into decisions earlier next time.

A second useful model, borrowed loosely from air traffic control, is that your job in a live discussion is less about generating the best idea yourself and more about sequencing - making sure the right question gets asked before the room commits to an answer. A senior engineer with deep MongoDB experience might silently notice that a proposed schema will blow past the document size limit under realistic growth, but if the meeting has already moved on to deployment topology, that concern needs a facilitator to pull it back to the surface. Leading discussions is disproportionately about noticing when an important point has been dropped and returning to it, not about being the person who raises the most points originally.

Key Takeaways

Conclusion

Leading architectural discussions and driving cross-functional solutions is not a personality trait some engineers have and others do not; it is a set of habits - writing before meeting, naming trade-off axes explicitly, recording decisions, and knowing when to defer to organizational reality over technical purity. On a MERN and AWS stack, those habits have to be paired with genuine technical range, because the credibility that lets you facilitate a room comes partly from stakeholders trusting that you understand the consequences of the options on the table, from MongoDB's oplog behavior to Lambda cold starts to React's reconciliation model.

None of this replaces being a strong engineer; it sits on top of that foundation. The engineers who make the transition successfully tend to be the ones who stop treating architecture meetings as an extension of code review and start treating them as a distinct skill worth practicing deliberately - drafting more RFCs than strictly necessary, volunteering to facilitate discussions that are not yet theirs to own, and reviewing their own past decisions with the same rigor they'd apply to someone else's pull request. Confidence, in this context, is mostly the accumulated evidence that your process works, not an innate quality you either have or lack.

References