Harsh Mittal
Back to blog
2025-08-28·5 min read

When a \"Senior Flutter Developer\" Pull Request Made Me Rethink Experience

FlutterCode ReviewMentorshipAlso on Medium

When a Senior Flutter Developer Pull Request Made Me Rethink Experience

A candidate can say all the right words in an interview — Bloc, Riverpod, SOLID, clean architecture — and still submit a pull request with a 900-line build() method containing API calls, database writes, an unbounded in-memory cache, and navigation logic all inlined into one StatelessWidget. The detail that actually stopped the review cold was a catch block that yielded a success state regardless of what failed — network error, invalid payment, server error, it didn't matter, the UI would still show "Booking Confirmed." That's not inexperience so much as years of practicing the same unexamined habits without anyone catching them.

The more interesting part of the story is what came after: instead of rejecting the hire, the team chose to mentor — pairing him with a patient senior engineer and treating every subsequent review as a teaching moment about separating logic from UI, modeling failure states explicitly, and profiling instead of guessing at performance. Four weeks in, the same person's PRs looked unrecognizable; six months in, he was pushing back on his own code as "over-engineered" and asking for simplifications — which is a much better signal of growth than any resume line about years of experience.

The PR, in More Detail

The 900-line build() method wasn't just long — it mixed concerns that should never share a method: a network call fetching booking availability directly inside the widget build, a raw database write committing a reservation before payment confirmation actually returned, an in-memory Map acting as a cache with no eviction policy (a slow, silent memory leak that would only show up after hours of real usage, not in a quick manual test), and Navigator.push calls scattered inside conditional branches deep in the render logic. Any one of these would be a flag; all four together in a single method meant the code had no separation between "what the screen looks like" and "what the app actually does" — testing any piece in isolation was effectively impossible without triggering all the others.

The catch block was the detail that mattered most, though, because it wasn't a mistake of omission — it was an active choice, presumably made under deadline pressure at some earlier point, to make error states invisible rather than handle them: try { await confirmBooking(); return BookingResult.success(); } catch (e) { return BookingResult.success(); }. A user whose payment failed, whose network dropped, or who hit a legitimate server error would see the exact same "Booking Confirmed" screen as someone whose booking genuinely succeeded — which isn't a bug that shows up in a demo, it's one that shows up as a support ticket from a real user weeks later, disputing a charge for a booking that was never actually created.

Why Wasn't This Simple Inexperience?

Years of shipping code without anyone catching these specific patterns doesn't make someone a bad engineer — it means the patterns were never named as problems in the environments this person worked in before. Fluency with the vocabulary (Bloc, SOLID, clean architecture) coexisting with code that violates all three isn't hypocrisy; it's the gap between knowing terms and having internalized why they matter, which only closes through direct, specific feedback on real code — the kind an interview process rarely provides, and the kind most teams are too polite or too rushed to give during normal code review either.

Choosing to Mentor Instead of Reject

The decision that mattered most wasn't technical — it was the choice to treat this as a coaching opportunity rather than a hiring mistake to quietly correct. Pairing him with a patient senior engineer, and treating every subsequent code review not as a gate to pass but as a specific teaching moment, was deliberate: pointing at the exact line where UI and logic mixed and asking "what happens if this network call fails partway through this build method," rather than just requesting the fix and moving on.

The three things mentoring focused on specifically: separating business logic from UI so each could be reasoned about and tested independently, modeling failure states explicitly (the sealed-class pattern that makes a silent catch-and-succeed impossible to write by accident) instead of letting exceptions vanish into optimistic-looking code, and profiling actual behavior instead of guessing — replacing "I think this cache is fine" with a DevTools session showing exactly what it does under real usage.

The Trajectory

Four weeks into consistent, specific feedback, the same developer's pull requests were structurally unrecognizable from the first one — logic separated from UI, explicit error handling, tests that actually covered failure paths. Six months in, the more telling shift was in the questions being asked — pushing back on his own code as "over-engineered," asking whether a given abstraction was actually earning its complexity, proposing simplifications unprompted. That's a stronger signal of genuine growth than any interview performance or resume line about years of experience, because it reflects internalized judgment rather than pattern-matching against remembered vocabulary.

What This Changed About How We Hire and Review

The lesson wasn't "lower the bar" — the original PR was genuinely a serious problem, and shipping it would have been a real production risk. The lesson was that a bad first pull request, even a genuinely bad one, isn't always a reliable signal about whether someone can become a strong engineer with the right feedback — sometimes it's a signal about what they were never taught to notice, and specific, sustained mentorship closes that gap faster and more reliably than most hiring processes are designed to detect it in the first place.

Get new posts by email

No spam, no schedule — just an email when a new post goes up.