--- title: "What I Learned Shipping 300 Code Reviews in 1.5 Years as a Fresher" slug: what-300-code-reviews-taught-me date: 2026-01-05 updated: 2026-01-05 tags: - code description: "From fixing undefined variables to decomposing a monolith — a career progression compressed into data." status: published --- *From fixing undefined variables to decomposing a monolith — a career progression compressed into data.* --- ## The Numbers In 18 months as an IC-1 (fresher-level individual contributor), I authored 302 revisions on Phabricator: - **~200 published** (landed in production) - **~80 abandoned** (scrapped, restarted, superseded) - **~20 in other states** (needs review, draft) That's roughly 2.5 published revisions per week, sustained over 1.5 years. But the numbers alone don't tell the story. The *type* of work changed dramatically from month 1 to month 18. --- ## Phase 1: The Bug Fix Grind (Months 1-6) My first 100+ revisions were almost entirely bug fixes: ``` D27158: Fixes Undefined var TypeError: primarySkills is undefined D27181: Fix no space between text area and rating symbols D27293: Fixed undefined var TypeError: $element[0] is undefined D27295: Fixes Undefined var Error: filters is undefined D27310: Fixes Resume modal: No space after comma D27370: Fixes Undefined var Error: action_stages is undefined D27398: Fixed tooltip not showing for request evaluation ``` These look small. They are small. But here's what they taught me: ### What I Learned From Bug Fixes 1. **How to read a stack trace.** Sentry errors with JS undefined vars forced me to trace execution flow through AngularJS controllers and services. 2. **How the codebase is structured.** Every bug fix required understanding which controller owns which scope variable, which service fetches which data, which template renders which state. 3. **How to write a good revision summary.** Early on, my summaries were "Fix bug." By month 3, they included: what the bug was, what caused it, what I changed, and how to test it. 4. **How to handle the review process.** My first revisions had 3-4 rounds of review feedback. By month 4, most landed in 1-2 rounds. 5. **Pattern recognition.** After fixing 20 undefined variable bugs, I started seeing the anti-patterns that produced them. This became useful later when I started writing features. ### The Uncomfortable Truth Bug fixes are not glamorous. Nobody's impressed by "Fixed undefined var." But they're the fastest way to build: - Codebase familiarity - Trust from the team - Confidence to make larger changes --- ## Phase 2: Small Features and Polish (Months 6-10) Around month 6, my revisions shifted: ``` D29044: Fix cancel evaluation modal width and height issue D29884: Fix evaluation bugs for interviewer role D30267: Fix evaluation tab count issue changing issue on applying filters D30401: Add graceful handling for profile type mismatch D30483: Simplify evaluation count logic in templates D31016: Return Credit Info and Failure Message on Bulk Action Errors ``` The last one — `Return Credit Info and Failure Message on Bulk Action Errors` — was my first "feature" revision. Not a bug fix. Not a UI tweak. A deliberate backend enhancement that required designing a response format and coordinating with the frontend. ### What Changed - I started proposing solutions instead of being assigned bugs - I began touching the Python/Django layer, not just JS/CSS - I started owning small end-to-end flows (API → frontend → user message) --- ## Phase 3: Architecture and Performance (Months 10-18) This is where the work got interesting: ``` D28249: Separate Python Code for Evaluations D28269: Separated CSS Code For Evaluations D28365: Separate JS Code For Evaluations D28367: Separate HTML Code for Evaluations D28427: merge code-separation into master D34678: Add Custom Browser Find Feature D35117: Optimize Queries in get_candidate_stages API D36701: Log function arguments for celery tasks D36704: Restrict registering app APIs into base v1 API D37393: Improve performance of GET /candidate/{identifier} ``` I was now: - **Decomposing the monolith** — multi-revision architectural changes - **Optimizing queries** — reading EXPLAIN plans, adding select_related - **Building features from scratch** — the custom Ctrl+F search - **Writing infrastructure tooling** — lint rules, logging - **Documenting methodology** — Phriction docs for the team --- ## The Abandoned Revisions 80 out of 302 revisions were abandoned. Early on, this felt like failure. Now I see it differently. Common reasons for abandonment: ### 1. Wrong approach discovered during review ``` D37385: Improve performance of GET /candidate/{identifier} - API Group (Abandoned → superseded by D37393 with a better approach) ``` I'd write a solution, get review feedback that a different approach was better, and start fresh instead of frankensteining the original. ### 2. Exploratory revisions ``` D35740: Logging for T43722 D35011: Logging for T42059 ``` These were diagnostic revisions — add logging, observe production behavior, remove logging. The logging itself was never meant to ship permanently. ### 3. Scope too large, needed to split ``` D34541: Move all template logic to JS Code for Easier Debugging (Abandoned → broken into smaller focused revisions) ``` ### 4. Genuine mistakes ``` D36712: Test linter added in D36704 (Oops, committed a test revision) ``` ### The Lesson **Abandoned revisions are cheap. Bad code in production is expensive.** If your alternative to abandoning is shipping something you're not confident in, abandon every time. The best engineers I work with have high abandon rates because they iterate aggressively. --- ## What I Wish I'd Known on Day 1 ### 1. Read the codebase before writing code My first few revisions had embarrassing mistakes — reimplementing things that already existed, using patterns inconsistent with the rest of the code. Spend your first week reading, not writing. ### 2. Small revisions > large revisions My monolith decomposition landed as 5 separate revisions. If I'd tried to do it in 1 massive diff: - Reviewers wouldn't have reviewed it carefully - A revert would have been all-or-nothing - I couldn't have gotten incremental feedback Target 100-300 lines per revision. If it's bigger, ask: "Can this be split?" ### 3. Write the summary as if the reviewer knows nothing ``` # Bad summary Fix the bug # Good summary Fix: Evaluation count shows wrong value on search page after adding note Bug: After adding a note from the search page, the evaluation count badge increments by 1 even though no evaluation was submitted. Cause: The note-creation event triggers the same event listener that evaluation submission uses. The listener doesn't distinguish between event types. Fix: Add event type check in the listener. Only increment count when the event type is 'evaluation_submitted'. Test: Add note on search page → count stays the same. ``` The second version reviews itself. The reviewer can verify the fix just by reading the summary. ### 4. Understand the system, not just the symptom My best work came from asking "why does this pattern exist?" rather than "how do I fix this instance?" - Seeing repeated undefined var bugs → led me to understand AngularJS scope inheritance - Seeing repeated CSS conflicts → led me to propose app-scoped CSS - Seeing repeated API namespace collisions → led me to write the lint rule ### 5. Document what you learn I wrote two internal docs: - "How to Run Compress Locally" — because I spent 2 hours figuring it out and didn't want anyone else to - "How to Separate an App" — because the monolith decomposition process wasn't written down anywhere These took 30 minutes each. They saved hours for every new hire after me. --- ## The Progression, Visualized ``` Month 1-3: ████████████████████████ Bug fixes (undefined vars, CSS) Month 4-6: ████████████░░░░░░░░░░░░ Bug fixes + UI polish Month 7-9: ████████░░░░░░░░████████ Features + App separation Month 10-12: ████░░░░░░░░████████████ Performance + Architecture Month 13-18: ██░░░░████████████████░░ Architecture + Infra + Features ``` The ratio of "fix someone else's bug" to "build something new" steadily inverted. --- ## Metrics That Actually Matter Looking back, these are the metrics that correlated with real growth: | Metric | Why It Matters | |--------|---------------| | **Revisions per week** | Consistency > bursts. 2-3/week means you're reliably shipping. | | **Lines of code per revision** | Smaller = better reviewed = higher quality. | | **Review rounds before landing** | Decreasing over time means you're internalizing the codebase standards. | | **Scope of changes** | Are you touching 1 file or 10? Single-layer or full-stack? | | **Who assigns your work** | Self-assigned > assigned. It means you're spotting problems. | | **Abandoned-to-published ratio** | A healthy ratio (~20-30%) means you're experimenting, not just playing safe. | --- ## Advice for Other Freshers 1. **The bug fix phase is an investment, not a punishment.** You're building a mental model of the system. This pays off exponentially when you start building features. 2. **Ship frequently, not perfectly.** A revision that ships with one round of feedback is better than a "perfect" revision that takes a week. 3. **Go beyond your ticket.** If you fix a bug and notice two more, fix those too. If you see a pattern, propose a systemic fix. This is how you get trusted with architecture work. 4. **Read other people's revisions.** I learned more from reviewing my teammates' code than from writing my own. You see patterns, approaches, and mistakes you can avoid. 5. **Own a module.** After 6 months, I became "the evaluations person." This wasn't assigned — I just kept fixing evaluations bugs until I understood it better than anyone. Then the decomposition work was a natural next step. 6. **Write things down.** Two 30-minute docs earned me more visibility than 50 bug fixes. Documentation signals that you're thinking about the team, not just your own output. --- ## Where It Led After 18 months: - Owned the full evaluations module end-to-end - Optimized 4 high-traffic API endpoints - Built architectural tooling (lint rules, logging) - Documented repeatable engineering processes - Contributed to the Python 3 migration From a career positioning standpoint, this body of work is equivalent to what many engineers accumulate in 3-4 years — because the volume was high, the scope expanded continuously, and I documented everything. The 302 revisions aren't the point. The arc from "fix undefined variable" to "decompose the monolith" — that's the story.