<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Afeh</title>
    <description>The latest articles on DEV Community by Afeh (@afeh).</description>
    <link>https://dev.to/afeh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F618156%2F6bc8b44f-1632-439b-9dec-c3c63385bd06.png</url>
      <title>DEV Community: Afeh</title>
      <link>https://dev.to/afeh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/afeh"/>
    <language>en</language>
    <item>
      <title>Engineering Resilience: Two Lessons from Building Under Pressure</title>
      <dc:creator>Afeh</dc:creator>
      <pubDate>Sat, 13 Jun 2026 09:10:02 +0000</pubDate>
      <link>https://dev.to/afeh/engineering-resilience-two-lessons-from-building-under-pressure-1b69</link>
      <guid>https://dev.to/afeh/engineering-resilience-two-lessons-from-building-under-pressure-1b69</guid>
      <description>&lt;p&gt;&lt;strong&gt;A reflection on performance optimization at scale and building reliability mechanisms; two tasks that defined my internship.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Every engineering internship has its share of "aha" moments; those late-night debugging sessions where a breakthrough finally clicks, or the PR that takes seven commits to get right. As I wrap up my time as an Intern with HNG, I want to write about two tasks that stuck with me. Not because they were the hardest, but because they taught me something real about building systems that have to work.&lt;/p&gt;

&lt;p&gt;One was individual, optimizing a demographic intelligence API to handle millions of records with sub-second query times. The other was a team effort; building reliability mechanisms into an AI-powered interview platform so that when things break (and they will), the system degrades gracefully instead of falling apart.&lt;/p&gt;

&lt;p&gt;Lets walk through both.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part I: Working on Insighta (Individual Task — Stage 4B)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it was
&lt;/h3&gt;

&lt;p&gt;Insighta IQ is a demographic intelligence API; think "find me Nigerian females aged 20 to 45." I nicknamed it Stereo API(Stereo short for stereotyping of course). Users query a PostgreSQL database of millions of demographic profiles through a FastAPI backend, via both CLI and web clients.&lt;/p&gt;

&lt;p&gt;Stage 4 asked us to make it perform under serious assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data growth&lt;/strong&gt;: from millions toward tens of millions of profiles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic&lt;/strong&gt;: hundreds to low thousands of queries per minute, multiple teams using it daily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance targets&lt;/strong&gt;: P50 latency under 500ms, P95 under 2 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The task had three parts: query optimization, query normalization, and large-scale CSV ingestion. The CSV ingestion piece was the one that kept me up at night.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Users needed to upload CSV files with up to &lt;strong&gt;500,000 rows&lt;/strong&gt; of profile data. These weren't trivial constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You cannot insert rows one by one (500k individual inserts = death by a thousand round trips)&lt;/li&gt;
&lt;li&gt;You cannot load the entire file into memory (500k rows of profile data can easily exceed available RAM)&lt;/li&gt;
&lt;li&gt;Uploads must not block ongoing query traffic (the system can't go dark during ingestion)&lt;/li&gt;
&lt;li&gt;The system must handle concurrent uploads&lt;/li&gt;
&lt;li&gt;A single bad row must never fail the entire upload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that, the database is hosted remotely, so every query — including every INSERT, incurs network latency. And we were already under read pressure from the query workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I approached it
&lt;/h3&gt;

&lt;p&gt;I broke the problem into layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Streaming, not loading&lt;/strong&gt;: Read the file in 256KB chunks, never hold the whole thing in memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch processing, not row-by-row&lt;/strong&gt;: Validate rows, accumulate 10,000 valid ones, then bulk-insert&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated thread pool&lt;/strong&gt;: Run the CPU+DB-bound CSV work off the event loop so the API stays responsive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-loading the uploading to a background task&lt;/strong&gt;: It is better to just return a success response to let the user know that the uploading is working in the background rather than waiting endlessly for the upload to get done. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial failure tolerance&lt;/strong&gt;: Each batch commits independently; failures don't roll back valid work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive skip reporting&lt;/strong&gt;: Return a structured summary of exactly what went wrong and why&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what the ingestion pipeline looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File upload → Chunked read (256KB) → Decode (UTF-8 → Latin-1 fallback)
  → CSV DictReader (streaming) → Validate row → Batch (10k rows)
    → Deduplicate in-batch → Single DB query for existing names
      → Bulk INSERT (ON CONFLICT DO NOTHING) → Report summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What broke and how I fixed it
&lt;/h3&gt;

&lt;p&gt;The first approach was naive. I read the entire CSV into memory, parsed every row, and then tried to insert everything at once. For a 100KB test file, it worked beautifully. For the 500,000-row file? Memory exploded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: I switched to streaming with &lt;code&gt;csv.DictReader&lt;/code&gt; over a &lt;code&gt;StringIO&lt;/code&gt; buffer. But I still needed to validate rows against each other (duplicate names within the same upload) and against the database. That meant holding state across batches of 10,000 rows.&lt;/p&gt;

&lt;p&gt;The second problem was &lt;strong&gt;intra-batch deduplication&lt;/strong&gt;. Without it, two rows with the same name in the same upload would both pass validation, only to conflict during insert. The fix was a &lt;code&gt;global_seen: Set[str]&lt;/code&gt;; a set of all names already inserted in this upload session, passed between batch flushes.&lt;/p&gt;

&lt;p&gt;The third problem was &lt;strong&gt;edge cases in CSV data I never anticipated&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rows with extra commas producing wrong column counts&lt;/li&gt;
&lt;li&gt;UTF-16 encoded files that failed UTF-8 decoding&lt;/li&gt;
&lt;li&gt;Age values like &lt;code&gt;"twenty-five"&lt;/code&gt; instead of &lt;code&gt;"25"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Country codes in varying cases (&lt;code&gt;"ng"&lt;/code&gt;, &lt;code&gt;"NG"&lt;/code&gt;, &lt;code&gt;"Ng"&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each edge case got its own validator function. The &lt;code&gt;_parse_row&lt;/code&gt; function became a gauntlet of pure functions — no database calls, just deterministic validation. If a row failed any check, it was counted and skipped, but processing continued.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I took away
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Batch everything. Validate early. Never trust user input.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The batch size of 10,000 wasn't arbitrary. Below 5,000, the overhead of DB round trips dominated. Above 20,000, memory pressure started climbing without significant throughput gains. 10,000 was the sweet spot.&lt;/p&gt;

&lt;p&gt;The pattern that emerged; stream, validate, batch, insert, report — is something I now see everywhere: ETL pipelines, message queues, log processors. It's a universal pattern for handling lots of data with limited resources.&lt;/p&gt;

&lt;p&gt;The caching and query optimization work (indexes on &lt;code&gt;gender&lt;/code&gt;, &lt;code&gt;age&lt;/code&gt;, &lt;code&gt;country_id&lt;/code&gt;, composite indexes, connection pooling, result caching with 5-minute TTL) brought the query side in line with our P50/P95 targets. But the CSV ingestion piece — that's what I remember most vividly, because it forced me to think about resource constraints, failure modes, and graceful degradation all at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I picked this
&lt;/h3&gt;

&lt;p&gt;The CSV ingestion task wasn't just about writing code. It was about &lt;strong&gt;engineering for constraints&lt;/strong&gt;: memory, concurrency, consistency, partial failure. Every decision (batch size, decoding fallback strategy, &lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt; vs pre-query) was a trade-off. I had to justify each one. That process of articulating &lt;em&gt;why&lt;/em&gt; a decision is right, not just that it works is what I think separates engineering from coding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part II: Building Reliability Into an AI Interview Platform (Team Task)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What it was
&lt;/h3&gt;

&lt;p&gt;MeetMind is an AI-powered interview platform. Candidates join live audio interviews with an AI interviewer, and the system records transcripts, generates assessments, sends emails, and provides a chat interface for recruiters to query interview data. I really had a lot of fun building this with my teammates (though I nearly lost my mind a couple of times).&lt;/p&gt;

&lt;p&gt;The catch? It depends on several third-party APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; (Google) for generating interview questions, assessments, and extracting resume information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resend&lt;/strong&gt; for transactional email delivery (welcome emails, password resets, interview invites)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LiveKit&lt;/strong&gt; for real-time audio/video sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any of these could fail at any moment from network blips, rate limits, service outages, or transient errors. Before this task, a failed API call meant a 500 error and a frustrated user.&lt;/p&gt;

&lt;p&gt;My PR to address that issue added two reliability mechanisms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Retry with exponential backoff&lt;/strong&gt; — a centralized &lt;code&gt;retry_async&lt;/code&gt; utility that wraps any async function with automatic retries, logging, and backoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcript fallback&lt;/strong&gt; — when individual transcript turns are missing in the database, fall back to the session's stored transcript JSON&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Two concrete failure scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario A:&lt;/strong&gt; A candidate finishes an interview, and the system fires off a background task to generate an assessment summary via Gemini. Halfway through, Gemini returns a 429 (rate limit). The assessment fails. The summary is stuck in "generating" status. The recruiter sees a blank report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario B:&lt;/strong&gt; Real-time interview transcript turns are stored one by one as the AI interviewer and candidate speak. If the persistence pipeline drops a few turns — or fails entirely — the transcript is incomplete. The recruiter can't review the interview. The AI can't generate an assessment.&lt;/p&gt;

&lt;p&gt;Both scenarios had the same root cause: &lt;strong&gt;no mechanism for transient failure recovery&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I approached it
&lt;/h3&gt;

&lt;p&gt;For the retry mechanism, I wanted something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works with any async function (generic, reusable)&lt;/li&gt;
&lt;li&gt;Uses exponential backoff (don't hammer the failing service)&lt;/li&gt;
&lt;li&gt;Logs warnings on intermediate failures, errors on exhaustion&lt;/li&gt;
&lt;li&gt;Accepts configurable retry count, delay, backoff factor, and exception types&lt;/li&gt;
&lt;li&gt;Integrates with existing code without massive refactoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The signature was clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[...,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;initial_delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;backoff_factor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;BaseException&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
    &lt;span class="n"&gt;task_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the transcript fallback, the pattern was: try the primary data source first, and if unavailable, reconstruct from the session's &lt;code&gt;transcript_json&lt;/code&gt; field. The fallback had to be transparent to callers — &lt;code&gt;get_chat_history&lt;/code&gt;, &lt;code&gt;get_transcript&lt;/code&gt;, and &lt;code&gt;get_transcript_export&lt;/code&gt; all work the same way regardless of which data source backs them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What broke and how I fixed it
&lt;/h3&gt;

&lt;p&gt;The first bug was an off-by-one in the backoff calculation. I had &lt;code&gt;delay *= backoff_factor&lt;/code&gt; happening &lt;em&gt;before&lt;/em&gt; the sleep, so the first retry was 2x the initial delay instead of the initial delay itself. It felt trivial, but it meant retries took longer than necessary — 2s, 4s, 8s instead of 1s, 2s, 4s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Moved the &lt;code&gt;delay *= backoff_factor&lt;/code&gt; to after the sleep.&lt;/p&gt;

&lt;p&gt;The second issue was &lt;strong&gt;exception type granularity&lt;/strong&gt;. Initially, the retry caught all &lt;code&gt;Exception&lt;/code&gt; subclasses. But some exceptions shouldn't be retried — like &lt;code&gt;ValueError&lt;/code&gt; from bad user input, or &lt;code&gt;KeyError&lt;/code&gt; from a missing dictionary key. A retry won't fix a programming error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Made the &lt;code&gt;exceptions&lt;/code&gt; tuple a parameter so callers can specify which exceptions are retryable. For Gemini calls, we retry &lt;code&gt;(Exception,)&lt;/code&gt; since the API can fail for many transient reasons. For email delivery, we retry the Resend-specific exception.&lt;/p&gt;

&lt;p&gt;The third issue was &lt;strong&gt;the transcript fallback format mismatch&lt;/strong&gt;. The session's &lt;code&gt;transcript_json&lt;/code&gt; stored timestamps as Unix seconds, but the transcript endpoints expected them formatted as &lt;code&gt;"HH:MM:SS"&lt;/code&gt; elapsed time. The fallback function needed to reproduce the same relative timestamp calculation that the primary path used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Created &lt;code&gt;_format_elapsed_timestamp&lt;/code&gt; as a shared utility and used it in both the primary and fallback paths. The fallback also had to generate deterministic UUIDs for each fallback turn (using &lt;code&gt;uuid.uuid5&lt;/code&gt; with a namespace) since there were no database IDs available.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I took away
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Graceful degradation &amp;gt; perfect failure.&lt;/strong&gt; The goal isn't to never fail — it's to fail in a way that doesn't cascade into user-facing errors. The retry mechanism handles transient failures silently. The fallback mechanism ensures that even if the real-time pipeline drops data, users can still review interview transcripts. The user never needs to know something went wrong.&lt;/p&gt;

&lt;p&gt;I also learned that &lt;strong&gt;reliability is visible in the logs&lt;/strong&gt;. After the retry mechanism was deployed, we stopped seeing "Assessment generation failed" errors in Sentry. Instead, we saw &lt;code&gt;"Attempt 1/3 failed for Generate assessment... Retrying in 2.00s..."&lt;/code&gt; — which is an entirely different class of log. It means the system &lt;em&gt;handled&lt;/em&gt; the failure rather than &lt;em&gt;succumbing&lt;/em&gt; to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I picked this
&lt;/h3&gt;

&lt;p&gt;This task taught me that reliability isn't a feature you add at the end — it's a design philosophy that affects how you structure every external dependency call. The &lt;code&gt;retry_async&lt;/code&gt; utility now wraps every Gemini generation, every email send, every document embedding. It's invisible infrastructure. But it's the difference between a system that occasionally returns 500s and one that absorbs transient failures and moves on. It is important to build Fault-tolerant systems.&lt;/p&gt;

&lt;p&gt;And the transcript fallback taught me something subtler: &lt;strong&gt;data can come from multiple sources, and that's okay&lt;/strong&gt;. The real-time turns are the ideal data source. But the session JSON is always there as a safety net. Engineering for multiple data paths — with clear fallback semantics — makes the system more robust without adding complexity to the API surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bringing It Together
&lt;/h2&gt;

&lt;p&gt;Looking back, both tasks taught me the same lesson from different angles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design for failure, optimize for reality.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The CSV ingestion system assumes every row could be bad and handles it gracefully without stopping. The retry mechanism assumes every API call could fail and recovers transparently. The transcript fallback assumes the primary data path could be incomplete and provides an alternative.&lt;/p&gt;

&lt;p&gt;Systems that work under pressure aren't the ones that never break. They're the ones that break gracefully, recover quickly, and leave useful evidence behind when they can't.&lt;/p&gt;

&lt;p&gt;That's what I'll carry forward from this internship.&lt;/p&gt;




</description>
      <category>api</category>
      <category>career</category>
      <category>performance</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Building a Dynamic Profile API with FastAPI: My HNG Stage 0 Experience</title>
      <dc:creator>Afeh</dc:creator>
      <pubDate>Mon, 20 Oct 2025 13:16:54 +0000</pubDate>
      <link>https://dev.to/afeh/building-a-dynamic-profile-api-with-fastapi-my-hng-stage-0-experience-4an4</link>
      <guid>https://dev.to/afeh/building-a-dynamic-profile-api-with-fastapi-my-hng-stage-0-experience-4an4</guid>
      <description>&lt;p&gt;During Stage 0 of the Backend Wizards program, I built a simple yet insightful REST API endpoint called /me using FastAPI. The goal was to return my personal profile information, fetch a random cat fact from an external API, include a dynamic UTC timestamp, and format everything neatly in JSON. While it looked straightforward, it pushed me to understand key backend concepts like API integration, error handling, and deployment. I structured the project using Python, FastAPI, and the requests library, managed environment variables with python-dotenv, and deployed it seamlessly on Railway. I learned to anticipate failures from external APIs by adding try-except blocks, format timestamps in ISO 8601, and configure environment variables properly for cloud deployment. Seeing my live endpoint— &lt;a href="https://introproj-production.up.railway.app/me" rel="noopener noreferrer"&gt;https://introproj-production.up.railway.app/me&lt;/a&gt;&lt;br&gt;
—work flawlessly was satisfying. More than just completing a task, this experience taught me how much discipline and attention to detail backend development truly demands, from clean code structure to reliable production deployment.&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>api</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Doing Hard Things</title>
      <dc:creator>Afeh</dc:creator>
      <pubDate>Tue, 02 Jul 2024 09:28:46 +0000</pubDate>
      <link>https://dev.to/afeh/doing-hard-things-227f</link>
      <guid>https://dev.to/afeh/doing-hard-things-227f</guid>
      <description>&lt;h2&gt;
  
  
  "What doesn't kill you makes you stronger" - A strong man(probably).
&lt;/h2&gt;

&lt;p&gt;On the 9th of March, 2024, I woke up around 6 am. It was a Saturday so it was unusual for me to be up that early. I said my prayers, picked up my phone and opened WhatsApp. Scrolling through unread messages, a post in my school's Google Developer Student Club group caught my eye. "Django Developer Needed. DM." &lt;em&gt;Interesting&lt;/em&gt; I thought. After contemplating for some minutes and going through some moments of self-doubt, I opened the group and messaged the person who posted it. &lt;/p&gt;

&lt;p&gt;There was a catch. He said he was in need of a Django Developer with workable knowledge of Bootstrap. &lt;em&gt;Bruhhh&lt;/em&gt; I thought. I was a decent Django Developer, but front-end stuff like Bootstrap and CSS? Not my thing. I had a little chat with God on it and I felt cool about it. Taking a deep breath, I told the guy I'd take the gig. We discussed my charge and deadline- I had 24 hours to come up with a working Application. I said "No p", trying to sound confident(even though this was going to be my first paid gig, ever).&lt;/p&gt;

&lt;p&gt;There was no power in my hostel, so I packed my Laptop, my charger, Rubik's Cube(for when my brain stops cooperating) and notepad into my bag, and had my bath, drank tea, and headed to the co-working space on campus. While walking, I brainstormed about how the structure of the App would be- team management features, am I using Class Based Views or Function Based Views?, stuff like that. Half-excitement, half-terror tightened my chest. Had I bitten more than I can chew?? &lt;/p&gt;

&lt;p&gt;I got to the co-working space, paid the fee, took a seat. I booted my Laptop, opened VSCode and just stared. Instead of the usual thrill of building something new, a wave of imposter syndrome washed over me. &lt;em&gt;Omoo can I actually do this thing?&lt;/em&gt; I closed my eyes, said a quick prayer and started working. I had a deadline to beat.&lt;/p&gt;

&lt;p&gt;The next 15 hours were a blur of coding, troubleshooting, and frantic calls to friends for help and moral support. The only breaks I took were to solve my Cube and eat. I had never worked that long in my short coding life before and the exhilarating feeling I had when I made my final push to GitHub might just be similar to what Neil Armstrong felt when he took that first step on the moon. &lt;em&gt;One small step for Afebu and a giant leap for his coding career lol&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I can do all things..." Paul (f.k.a Saul) from Tarsus&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Last month, I saw a post on X about HNG Internship 11. I had heard stories about how difficult, grueling and rigorous the whole process is. But then I remembered that 24-hour coding marathon. "Well, well, well," I thought, a smile creeping across my face. "Hello hard things, I guess we meet again."&lt;/p&gt;

&lt;p&gt;If you are interested in doing hard things too &lt;a href="https://hng.tech/internship"&gt;click here&lt;/a&gt; to register for the HNG Internship.&lt;/p&gt;

&lt;p&gt;I am Afebu Victor Balogun. This is my story, and I am sticking to it.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
