Practical lessons from my first three years as a software engineer—impact, communication, code quality, and ownership—with examples you can apply today.

Breaking into the industry felt like sprinting into a marathon. In my first three years, I learned that leveling up is less about knowing every framework and more about compounding small professional habits: communicating clearly, writing maintainable code, testing with intention, and focusing on business impact. This post distills the practices that moved me from task‑taker to trusted engineer.
Early on, I equated productivity with number of tickets closed. Senior engineers optimize for business outcomes: fewer incidents, faster load times, higher conversion, lower costs.
Instead of rebuilding a service, I reduced cold‑start latency by caching config and lazy‑loading non‑critical deps. The result cut p95 request time by 28% with one week of work.
// before: config loaded and parsed on every request
app.get('/price', async (req, res) => {
const config = await loadConfig();
const price = computePrice(config, req.query);
res.json({ price });
});
// after: config cached and refreshed on interval
let cachedConfig: Config | null = null;
async function getConfig() {
if (!cachedConfig) cachedConfig = await loadConfig();
return cachedConfig;
}
setInterval(async () => (cachedConfig = await loadConfig()), 5 * 60_000);Practical tip
Seniors don’t just write code. They reduce uncertainty for others.
Before starting a feature, I send a 1‑page proposal with problem, scope, risks, and timeline. Feedback arrives async, decisions are documented, and I avoid rework.
# RFC: Feature Flags for Checkout
- Problem: Risky releases cause rollbacks
- Proposal: Introduce gradual rollout via server‑side flags (ConfigCat)
- Scope: Backend gating + metrics + owner
- Risks: Flag drift, stale code paths
- Timeline: 1 weekPractical tip
Readable code survives org changes. I learned to optimize for the next person (often future‑me).
I adopted a layered approach: route → handler → service → repository. Each layer has a single reason to change.
// handler.ts
export async function createUserHandler(req: Request, res: Response) {
const input = parse(req.body, createUserSchema); // validation
const user = await userService.create(input); // orchestration
res.status(201).json(user);
}
// user.service.ts
export async function create(input: CreateUserInput) {
if (await userRepo.exists(input.email)) throw new Error('E_EXIST');
const hash = await hashPassword(input.password);
return userRepo.insert({ ...input, password: hash });
}ESLint, Prettier, and strict TypeScript prevent entire classes of issues.
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
}
}Practical tip
At first I wrote tests to raise coverage. Seniors write tests to prevent regressions and enable refactors.
I aim for a triangle of tests: a few end‑to‑end, more service‑level, and unit tests for tricky logic.
// service test (fast, meaningful)
describe('priceService.calculate', () => {
it('applies tiered discount and tax', () => {
const price = priceService.calculate({ base: 100, tier: 'pro', region: 'EU' });
expect(price.total).toBeCloseTo(108.5, 1);
});
});Write tests that read like documentation. Use factories over fixtures, and avoid mocking internals that will change.
Practical tip
A senior mindset includes operability.
Add structured logs and correlation IDs. Emit counters for success and errors. Create an on‑call runbook.
import pino from 'pino';
export const log = pino();
app.use((req, _res, next) => {
(req as any).cid = crypto.randomUUID();
log.info({ cid: (req as any).cid, path: req.path, method: req.method }, 'request');
next();
});Practical tip
Helping others is not charity—it’s leverage. Pairing, reviewing PRs with empathy, and sharing learning notes elevated the whole team.
I switched from “what’s wrong?” to “how can this be clearer?”
Instead of: "Rename this."
Try: "This function does two things (parsing and persistence). Consider splitting to improve testability."Practical tip
Seniors are consistent, not just brilliant on good days. I learned to guard deep‑work blocks and automate toil.
A few scripts saved me hours and reduced mistakes.
# scripts/redeploy.sh
set -euo pipefail
BRANCH=$(git rev-parse --abbrev-ref HEAD)
npm run test:ci
docker build -t app:$BRANCH .
docker push registry.example.com/app:$BRANCH
kubectl set image deploy/app app=registry.example.com/app:$BRANCHPractical tip
Becoming “senior” is not a title you receive after enough sprints. It is the habit of creating clarity, delivering measurable outcomes, and making those around you more effective. The techniques above—impact focus, precise communication, reader‑friendly code, meaningful tests, operational ownership, and mentorship—compound over time. Start small, measure results, and your growth will be undeniable.