<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Anas Khan's blog]]></title><description><![CDATA[Anas Khan's blog]]></description><link>https://blog.anaskhan.me</link><generator>RSS for Node</generator><lastBuildDate>Tue, 13 Oct 2026 10:12:18 GMT</lastBuildDate><atom:link href="https://blog.anaskhan.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I booked my IndiaFOSS ticket at the thirteenth hour]]></title><description><![CDATA[Anas Khan, September 2026
I have attended IndiaFOSS for the past four years. It is one of the few conferences I plan to attend before I know the schedule, because the people and the conversations are ]]></description><link>https://blog.anaskhan.me/indiafoss-ticket</link><guid isPermaLink="true">https://blog.anaskhan.me/indiafoss-ticket</guid><category><![CDATA[indiafoss]]></category><category><![CDATA[razorpay]]></category><category><![CDATA[Payment Integration]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[webhooks]]></category><dc:creator><![CDATA[Anas Khan]]></dc:creator><pubDate>Tue, 01 Sep 2026 05:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/015ac0d6-ba01-43df-b428-a79802dde13e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Anas Khan, September 2026</em></p>
<p>I have attended IndiaFOSS for the past four years. It is one of the few conferences I plan to attend before I know the schedule, because the people and the conversations are reason enough to go.</p>
<p>This year, despite following the event and knowing that I wanted to be there, I forgot to buy a ticket.</p>
<p>At 12:40 am, I remembered that I still had to buy a ticket, but it was too late. The ₹1500 Late Bird tier was no longer available through the normal booking flow, and the next obvious option was a ₹3000 Regular ticket. By the time I was done, it was 1 am, hence the title "thirteenth hour" :P</p>
<p>Luckily, I had been experimenting with something earlier, and that gave me an idea that might work. Read on to find out what happened and, if you have not worked with payment integrations before, learn a little about them along the way. This is a case study and a practical introduction to payment orders, embedded checkout, server-side amount validation, signatures, callbacks, and fulfillment.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/722ed692-4287-4d58-a065-33a34e78a520.png" alt="The IndiaFOSS ticket page with Late Bird unavailable and the Regular tier available" style="display:block;margin:0 auto" />

<p><em>The Late Bird window had closed around forty minutes earlier.</em></p>
<h2>A little background</h2>
<p>I am a software engineer and tinkerer. I have worked with payment integrations before and usually get excited about that space.</p>
<p>At <strong>FamPay</strong>, I worked on fintech backend systems, including recharge and merchant-payment flows. At my last job at <strong>HackerRank</strong>, I worked across payments and other product areas, including subscription and checkout services using <strong>Razorpay and Stripe</strong>. I spent a fair amount of time building and debugging everything myself (a nightmare sometimes), so I had a little experience there.</p>
<p>I had also examined payment and webhook behavior while studying Interview Coder's source code. That allowed me to replay a webhook and get premium accounts for free. I wrote about it here: <a href="https://blog.anaskhan.me/decoding-interview-coder">https://blog.anaskhan.me/decoding-interview-coder</a>.</p>
<p>There was one more helpful coincidence. FOSS United's platform is built on <strong>Frappe</strong>, and I previously interned at Frappe Technologies. Once I found the relevant backend methods, their conventions were familiar.</p>
<p>None of this meant I knew the answer in advance. It meant I knew which questions to ask:</p>
<ol>
<li><p>Had FOSS United already created a Razorpay order for ₹1500?</p>
</li>
<li><p>Was that order still payable after the ticket tier disappeared from the UI?</p>
</li>
<li><p>If payment succeeded, how did FOSS United verify and fulfill it?</p>
</li>
</ol>
<h2>The payment model: order, checkout, verification, fulfillment</h2>
<p>Before describing what I changed, it helps to understand a standard Razorpay integration.</p>
<h3>1. The merchant creates an order</h3>
<p>The merchant's backend decides what the customer is buying and calculates the amount. It then asks Razorpay to create an order.</p>
<p>The <code>order_id</code> identifies a specific payment attempt. The amount is set when the backend creates the order. A customer should not be able to edit the page and turn ₹1500 into ₹15 because the trusted amount lives on the server and at Razorpay.</p>
<h3>2. The browser opens Checkout</h3>
<p>The frontend loads Razorpay's Checkout JavaScript and gives it the public merchant key and order ID.</p>
<pre><code class="language-javascript">const checkout = new Razorpay({
  key: "rzp_live_...",
  order_id: "order_...",
  name: "FOSS United",
});

checkout.open();
</code></pre>
<p>The key ID is public by design. It identifies the merchant account. The corresponding secret must remain on the server.</p>
<h3>3. Razorpay returns a signed result</h3>
<p>After a successful payment, Checkout returns three important fields:</p>
<pre><code class="language-json">{
  "razorpay_order_id": "order_...",
  "razorpay_payment_id": "pay_...",
  "razorpay_signature": "..."
}
</code></pre>
<p>The signature binds the order and payment together. The merchant backend verifies it using the private Razorpay secret.</p>
<h3>4. The merchant fulfills the purchase</h3>
<p>Only after verification should the application mark the payment as captured and provide the purchased item. For FOSS United, fulfillment means creating an event ticket, making the PDF available, and sending the confirmation email.</p>
<p>The order and payment move through related but separate state machines.</p>
<p>Stripe's PaymentIntent flow follows a similar separation, although the APIs and terminology differ.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/fb4b335a-fcb1-438c-9e36-b6374c3388ee.png" alt="How FOSS United creates a Razorpay ticket order" style="display:block;margin:0 auto" />

<p><em>The browser initiates the flow, but the backend and payment provider establish what was actually paid.</em></p>
<h2>What the coding-agent experiment left behind</h2>
<p>A couple of days back, I had asked my coding agent to navigate the ticket flow for fun. I wanted to see whether an agent could inspect availability, select the tier, fill attendee information, and reach checkout.</p>
<p>It reached the correct boundary: the Razorpay payment screen. The agent could prepare the transaction, but I still had to approve the payment.</p>
<p>By the time I returned after midnight, the official page no longer allowed me to select Late Bird. The unfinished flow had nevertheless created an order ID for that tier.</p>
<p>An order ID is not a payment link. It cannot be pasted into a browser by itself. However, it might be useful somewhere else. I just needed to figure out where. Since I had worked on similar embedded pages at HackerRank, I suspected that reconstructing the missing final screen without changing the order or its amount might work.</p>
<p>First, I needed to confirm how FOSS United implemented the flow.</p>
<h2>Reading FOSS United's open source implementation</h2>
<p>FOSS United is open sourced and its platform is hosted on GitHub. I followed the payment path through three files:</p>
<pre><code class="language-text">dashboard/src/pages/BuyTickets.vue
dashboard/src/components/common/RazorpayCheckout.vue
fossunited/api/dashboard.py
</code></pre>
<h3>Server-side amount calculation</h3>
<p>The first backend function I cared about was <code>create_razorpay_order</code> and the helper it used to calculate the total.</p>
<p>The implementation recomputed the amount using ticket-tier prices stored in the database. It also included optional T-shirt charges where applicable. It compared that server result with the amount provided by the client and rejected mismatches.</p>
<p>In simplified form:</p>
<pre><code class="language-python">amount = compute_order_amount(ticket_tiers, attendees)
client_amount = checkout_info["amount"]

if amount != client_amount:
    raise ValidationError("Amount mismatch")

order = razorpay.order.create({
    "amount": amount_in_paise,
    "currency": "INR",
})
</code></pre>
<p>The backend then stored a pending payment record containing:</p>
<ul>
<li><p>the Razorpay order ID</p>
</li>
<li><p>the calculated amount</p>
</li>
<li><p>the buyer email and billing information</p>
</li>
<li><p>the selected tier and attendee metadata</p>
</li>
<li><p>a <code>Pending</code> status</p>
</li>
</ul>
<p>This was important for two reasons. I could not manipulate the browser into buying the tier for a different price, and the attendee information had already been associated with the pending order.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/a77ad7c1-141a-47b4-a293-bdfd0e3fdcff.png" alt="Server-side Razorpay order creation code" style="display:block;margin:0 auto" />

<p><em>FOSS United recalculates the amount from database-backed tier prices before creating the Razorpay order.</em></p>
<h3>Embedded Razorpay Checkout</h3>
<p>Next I read <code>RazorpayCheckout.vue</code>. The component loaded Razorpay's Checkout script and created a popup using the returned public key and order ID.</p>
<p>Its success handler sent the Razorpay response to FOSS United:</p>
<pre><code class="language-javascript">handlePaymentSuccess(response) {
  post("handle_payment_success", {
    order_id: response.razorpay_order_id,
    payment_id: response.razorpay_payment_id,
    signature: response.razorpay_signature,
  });
}
</code></pre>
<p>This established that FOSS United used an <strong>embedded checkout</strong>, not a permanent Razorpay payment link for each ticket.</p>
<p>The distinction is useful:</p>
<table>
<thead>
<tr>
<th>Checkout type</th>
<th>How it works</th>
</tr>
</thead>
<tbody><tr>
<td>Hosted payment link</td>
<td>The customer visits a Razorpay-hosted URL such as an <code>rzp.io</code> link</td>
</tr>
<tr>
<td>Embedded checkout</td>
<td>The merchant loads Checkout.js and opens Razorpay inside its own page using an order ID</td>
</tr>
</tbody></table>
<p>FOSS United's original buy page was no longer able to recreate my Late Bird selection, but the embedded checkout itself only needed the existing order ID and public merchant key.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/5c4efc92-5fe5-4f7a-ac90-2959d55e2eb0.png" alt="RazorpayCheckout Vue component" style="display:block;margin:0 auto" />

<p><em>The official frontend opens Checkout with an order ID, then posts Razorpay's signed response to FOSS United.</em></p>
<h3>Signature verification</h3>
<p>Finally, I read <code>handle_payment_success</code> in the backend. It passed the order ID, payment ID, and signature to Razorpay's official verification utility. Only after successful verification did it update the pending payment to <code>Captured</code>.</p>
<p>In simplified form:</p>
<pre><code class="language-python">razorpay_client.utility.verify_payment_signature({
    "razorpay_order_id": order_id,
    "razorpay_payment_id": payment_id,
    "razorpay_signature": signature,
})

payment.status = "Captured"
payment.payment_id = payment_id
payment.save()
</code></pre>
<p>This was the security boundary. Calling the endpoint without a real payment would not work because I could not generate a valid signature. The experiment still required me to pay the exact amount attached to the existing order.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/c0570758-c345-46ea-a858-cb0ab5155628.png" alt="Razorpay signature verification code" style="display:block;margin:0 auto" />

<p><em>The backend verifies Razorpay's signature before marking the payment as captured.</em></p>
<h2>Reconstructing the checkout page</h2>
<p>After reading the code, I asked the agent to generate the smallest possible HTML page that mirrored the official Checkout component.</p>
<p>The page needed:</p>
<ul>
<li><p>Razorpay's Checkout.js script</p>
</li>
<li><p>FOSS United's public Razorpay key ID</p>
</li>
<li><p>the existing Late Bird order ID</p>
</li>
<li><p>a handler that displayed the payment response</p>
</li>
</ul>
<p>The core looked like this:</p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;script src="https://checkout.razorpay.com/v1/checkout.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;button id="pay"&gt;Pay ₹1500&lt;/button&gt;
    &lt;pre id="result"&gt;&lt;/pre&gt;

    &lt;script&gt;
      document.querySelector("#pay").onclick = () =&gt; {
        const checkout = new Razorpay({
          key: "rzp_live_...",
          order_id: "order_...",
          name: "FOSS United",
          description: "IndiaFOSS Late Bird Enthusiast",
          prefill: {
            name: "Anas Khan",
            email: "...",
          },
          handler(response) {
            document.querySelector("#result").textContent =
              JSON.stringify(response, null, 2);
          },
        });

        checkout.open();
      };
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>No private credentials were present. This page could only ask Razorpay to open an order that FOSS United had already created.</p>
<p>I opened it locally, waited for it to load, and clicked <strong>Pay ₹1500</strong>. The UPI QR code showed up. I thought the payment might fail or the order might have expired, but I grabbed my phone and tried anyway. Surprisingly, it went through.</p>
<p>That confirmed the key hypothesis: the Late Bird tier had expired in the storefront, but the existing Razorpay order was still open.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/4dc463f7-6612-4e2e-b173-87838924fc6e.png" alt="Local checkout page with masked order and email" style="display:block;margin:0 auto" />

<p><em>A minimal local page reproduced the embedded checkout used by the official frontend.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/2933b3eb-0b6c-48f6-8a24-d07e93bfaa10.png" alt="Razorpay Checkout showing FOSS United and a masked order" style="display:block;margin:0 auto" />

<p><em>Razorpay still recognized the existing ₹1500 order after the ticket tier had disappeared from the UI.</em></p>
<h2>Completing the payment and callback</h2>
<p>Then I made the real ₹1500 payment.</p>
<p>Once Razorpay captured the payment, I still wasn't sure whether I would get the tickets. I thought I might end up with a failure, which would have been a fun little mess. But, to my surprise, it worked. Here's what happened under the hood: two independent completion paths were available, a server-to-server webhook and a browser success callback. They carry different signatures and solve different reliability problems, but both reconcile the same pending order in FOSS United's database.</p>
<h3>The webhook path</h3>
<p>A <strong>webhook</strong> is an asynchronous HTTP request from one service to another when an event occurs. In this case, Razorpay can send FOSS United a <code>payment.captured</code> event after the charge is captured. This happens between servers and does not depend on my checkout tab remaining open.</p>
<p>The request includes the payment entity and its <code>order_id</code>. It also carries an <code>X-Razorpay-Signature</code> header. FOSS United computes an HMAC over the raw request body using the webhook secret configured in its Razorpay settings. If the signatures do not match, the event is rejected.</p>
<p>After verification, FOSS United's webhook handler:</p>
<ol>
<li><p>Writes a webhook log for auditability.</p>
</li>
<li><p>Looks up the <code>order_id</code> in its <code>Razorpay Payment</code> table.</p>
</li>
<li><p>Ignores unknown orders rather than creating records from untrusted input.</p>
</li>
<li><p>Changes the matching payment from <code>Pending</code> to <code>Captured</code> for a <code>payment.captured</code> event.</p>
</li>
<li><p>Saves the document, triggering Frappe's <code>on_update</code> hook.</p>
</li>
<li><p>Creates the ticket only if one has not already been created for that payment.</p>
</li>
</ol>
<p>Razorpay recommends webhooks as the reliable source of asynchronous payment state. If the customer closes the tab, loses connectivity, or never reaches the merchant's success page, the server can still reconcile the payment.</p>
<h3>The browser callback path</h3>
<p>Razorpay accepted it and returned the expected payload to my handler:</p>
<pre><code class="language-json">{
  "razorpay_payment_id": "pay_...",
  "razorpay_order_id": "order_...",
  "razorpay_signature": "..."
}
</code></pre>
<p>This is a separate Checkout signature, calculated from the order ID and payment ID using FOSS United's Razorpay key secret. The browser can carry the values, but it cannot generate a valid replacement signature.</p>
<p>Because this was a minimal page rather than the full FOSS United application, the normal post-payment experience did not complete cleanly. The popup remained visible and no ticket page appeared automatically. The payment result itself was available, though.</p>
<p>The official frontend would have posted those three fields to <code>handle_payment_success</code>, so I made the equivalent request manually:</p>
<pre><code class="language-bash">curl 'https://fossunited.org/api/method/…handle_payment_success' \
  -H 'content-type: application/json' \
  --data-raw '{
    "order_id": "order_...",
    "payment_id": "pay_...",
    "signature": "..."
  }'
</code></pre>
<p>I did not manually trigger the webhook. I reproduced the <strong>browser success callback</strong> that the official FOSS United frontend normally makes after Checkout returns.</p>
<p>The endpoint returned HTTP 200 with an empty JSON object:</p>
<pre><code class="language-json">{}
</code></pre>
<p>That initially looked ambiguous, but it matched the Frappe method's implementation: it completed its work without returning a response object. There was no signature error, and the pending payment had been captured.</p>
<p>The callback and webhook can arrive in either order. A robust payment integration must expect that race. FOSS United converges both paths on the same <code>Razorpay Payment</code> document, and its ticket hook checks whether a ticket already exists for that payment before creating one. That makes fulfillment idempotent: replaying a valid callback or receiving the webhook later should not mint another ticket.</p>
<p>I then opened FOSS United's payment-success route with the order ID, payment ID, and event ID. The ticket appeared, I downloaded its PDF, and the confirmation email arrived a few minutes later.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/938a09e1-e4e4-4003-9f12-6aeddbd6dad9.png" alt="" style="display:block;margin:0 auto" />

<p><em>Checkout returned the signed values needed by FOSS United's payment-success endpoint.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/00329526-d809-4d4f-94de-42409b4e53ad.png" alt="" style="display:block;margin:0 auto" />

<p><em>The request reproduced the callback made by FOSS United's official frontend.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/df20fb76-297f-45f6-95de-cb1ebcfee7ec.png" alt="" style="display:block;margin:0 auto" />

<p><em>The browser callback gives an immediate result while the signed webhook provides server-to-server reconciliation. Both converge on one idempotent fulfillment path.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/6a928255-f050-453d-8acf-4864ab8b15d2.png" alt="" style="display:block;margin:0 auto" />

<p><em>After signature verification, FOSS United created the Late Bird ticket.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/74704055-1ce3-4dd2-8095-e6f3718e0d3c.png" alt="" style="display:block;margin:0 auto" />

<p><em>The regular confirmation email arrived a few minutes later.</em></p>
<h2>Was this a security vulnerability?</h2>
<p>Not in the usual payment-security sense.</p>
<p>Razorpay Checkout is designed to run in browser code. The merchant's <code>key_id</code> and a created <code>order_id</code> are therefore visible to the customer. Reusing those public values on another page does not expose FOSS United's private Razorpay secret, change the order amount, redirect the money, or produce a valid payment without actually paying.</p>
<p>The local HTML page was only another container for the same Razorpay Checkout that the official page used. Razorpay still charged the ₹1500 amount associated with FOSS United's order. FOSS United still verified cryptographic proof of the payment before creating the ticket.</p>
<p>It would have been a serious vulnerability if I could send an invented payment ID, forge a webhook, or call a success endpoint without paying and still receive a ticket. That was not possible here.</p>
<h3>The part worth noting</h3>
<p>There is still a business-rule edge case here.</p>
<p>FOSS United validates the tier, price, enabled status, expiry date, and inventory when it creates the pending payment record. Razorpay Orders themselves do not automatically expire. An order that was valid when created can therefore remain payable after the ticket tier disappears from the storefront.</p>
<p>Some organizations would accept that as an informal grace period for customers who began checkout before the deadline. Others would not want discounted inventory sold after the advertised cutoff. If FOSS United wants a strict deadline, it should add an application-level expiry to pending payments and re-check that policy before fulfillment, with a defined refund path for a late payment.</p>
<p>I would describe this as an order-lifecycle or business-policy gap, not a severe payment vulnerability. The important security controls still held: the amount was set server-side, the payment was real, both callback and webhook paths required valid Razorpay signatures, and fulfillment was tied to an existing order in FOSS United's database.</p>
<h2>What about overselling?</h2>
<p>The same stale-order behavior matters for inventory, not only deadlines.</p>
<p>Imagine a tier with 500 tickets. Customer A starts Checkout while ticket 500 is still available, so FOSS United creates a pending Razorpay order. Customer B completes payment first and receives ticket 500. If Customer A pays the old order afterward, the order amount and signature are still valid, but the tier no longer has capacity.</p>
<p>FOSS United currently checks <code>maximum_tickets</code> when it creates the pending payment. After each ticket insert, it counts sold tickets and disables the tier when the limit is reached. Those checks protect the ordinary path, but a pending payment does not reserve inventory. At capture time, ticket creation does not repeat the tier-capacity check before inserting the ticket. Disabling the tier after the count reaches its maximum also does not undo an insertion that has already happened.</p>
<p>There are two related cases:</p>
<h3>A stale pending order after sellout</h3>
<p>One order sits unpaid while other buyers consume the remaining inventory. A fulfillment-time validation can catch this: lock the event, count committed tickets, compare the requested quantity with <code>maximum_tickets</code>, and avoid issuing tickets if capacity is gone.</p>
<h3>Concurrent captures for the last seat</h3>
<p>Two payments can complete at nearly the same time. A simple implementation like this is not sufficient:</p>
<pre><code class="language-python">if count_existing_tickets() + requested &lt;= maximum_tickets:
    create_tickets()
</code></pre>
<p>Both transactions might read 499, both decide that one seat remains, and both insert ticket 500. This is a classic time-of-check to time-of-use race.</p>
<p>The check and insertion need serialization. For example, fulfillment can acquire a database row lock on the ticket tier (or its parent event), then count and insert while holding that lock:</p>
<p>With serialization, the second transaction observes the first ticket before deciding. Another valid design is an atomic inventory counter or a short-lived reservation table, but both require more schema and lifecycle management.</p>
<p>The practical rule is: <strong>a Razorpay order reserves a payment amount, not a conference seat</strong>. Inventory reservation belongs in the merchant application.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/49faf77c-4165-41c1-8065-d1a32b9e026b.png" alt="Concurrent fulfillment and event row lock diagram" style="display:block;margin:0 auto" />

<p><em>A fulfillment-time capacity check must be serialized; otherwise two captures can both observe the same final seat.</em></p>
<h2>Four ways to handle expired or stale orders</h2>
<h3>1. Rely on Razorpay order expiry</h3>
<p>This is not available for Razorpay Orders. Razorpay documents that Orders are immutable and do not automatically expire. Its Checkout <code>timeout</code> option only closes a particular modal session; it does not invalidate the order. Payment Links support <code>expire_by</code>, but FOSS United uses Orders with embedded Checkout.</p>
<p>This can improve the user experience but cannot enforce a ticket deadline or inventory policy.</p>
<h3>2. Reject fulfillment and refund manually</h3>
<p>FOSS United could revalidate the tier after payment and refuse to create a ticket if it is expired or full. An operator could later refund the captured payment.</p>
<p>This is small, but it leaves the customer charged with no ticket until somebody handles the case. That is poor failure handling and creates unnecessary support work.</p>
<h3>3. Store an application-level <code>expires_at</code></h3>
<p>When creating the pending payment, FOSS United could store an immutable <code>expires_at</code> value. It could be the tier deadline or a shorter checkout window, depending on policy. Fulfillment would reject and refund payments received after that timestamp.</p>
<p>This makes the policy explicit and avoids relying on a tier record that may be edited later. It requires a schema change, migration, and compatibility behavior for older payment records. It also does not reserve inventory by itself.</p>
<h3>4. Revalidate at fulfillment and handle failures manually</h3>
<p>Both the browser callback and Razorpay webhook already converge on the same <code>Razorpay Payment.on_update</code> hook before ticket creation. That is the smallest central place to revalidate:</p>
<ul>
<li><p>event ticket sales are still live</p>
</li>
<li><p>every tier still belongs to the event</p>
</li>
<li><p>tiers remain enabled and within <code>valid_till</code></p>
</li>
<li><p>enough inventory remains for the entire payment</p>
</li>
<li><p>no ticket has already been created for this payment</p>
</li>
</ul>
<p>For inventory, this validation must hold a database lock across the capacity check and ticket inserts. If any rule fails, FOSS United should not create tickets. The accepted patch leaves the payment captured, logs the failure, and lets the team handle the case manually under its transfer-only policy.</p>
<p>This fourth approach covers both expired orders and stale orders paid after sellout. With the event lock, it also closes the concurrent-capture race. Without the lock, it only narrows the window and does not guarantee that inventory cannot be oversold.</p>
<p>Before publishing this write-up, I sent the payment-flow fix upstream as <a href="https://github.com/fossunited/fossunited/pull/1698">FOSS United pull request #1698</a>. My first version revalidated fulfillment, serialized capacity allocation, and automatically refunded captured payments that could no longer receive tickets.</p>
<p>The review changed that last part. FOSS United has a transfer-only policy and does not normally issue refunds, so the automatic refund worker and retry scheduler were removed. We also considered a 30-minute <code>expire_by</code> value, then dropped it after checking Razorpay's documentation: <code>expire_by</code> belongs to Payment Links and is rejected by the Orders API.</p>
<p>The parts that mattered most still merged on August 19:</p>
<ul>
<li><p>callback, webhook, and manual reconciliation use the same locked capture transition</p>
</li>
<li><p>fulfillment locks the parent event row before rechecking event status, tier validity, and capacity</p>
</li>
<li><p>replayed callbacks do not create duplicate tickets</p>
</li>
<li><p>a late successful capture can recover a locally failed payment</p>
</li>
<li><p>a failure callback cannot overwrite a payment that was already captured</p>
</li>
<li><p>unfulfillable captures are logged and left for manual resolution under the transfer policy</p>
</li>
</ul>
<p>That is the useful part of open-source review. I proposed one complete flow; the maintainers supplied operational context I did not have; we removed the pieces that did not fit their policy and kept the narrower fix.</p>
<p>A separate security review produced <a href="https://github.com/fossunited/fossunited/pull/1704">pull request #1704</a>, merged on August 21. That PR was not the stale-order fix and it was not my code. It restricted guest ticket responses to an explicit safe field list, reduced data returned by ticket-transfer and tier endpoints, constrained accepted check-in filters, and sanitized IndiaFOSS speaker descriptions before rendering them as HTML.</p>
<p>Both merged changes are now in the FOSS United project and running in production, so it is safe to write about this publicly. Also, none of y'all can try the same thing and cause a headache for the team anymore. Please don't treat that as a challenge.</p>
<h3>How far could this have gone?</h3>
<p>My ₹1500 Late Bird order was not even the cheapest version of this. IndiaFOSS also had an ₹800 Enthusiast tier that closed on 30 June, according to the <a href="https://fossunited.org/indiafoss/2026/stats">live stats</a>. So, in theory, somebody could have opened checkout back then, paid nothing, kept the pending Razorpay order around, and completed the ₹800 payment much closer to the event, esentailly hoarding it until you feel comforatable or make plans to attend closer to the event dates.</p>
<p>To be clear, this would not turn a new ₹3000 order into an ₹800 one. The amount was fixed when FOSS United created the order. The trick, if you want to call it that, was simply keeping an old valid order alive after the website stopped selling that tier.</p>
<p>It could get messier with sold-out tickets. The pending order did not reserve a seat, but the old fulfillment path also did not recheck whether the tier had expired, been disabled, or run out of capacity. An old order could therefore have been paid after sellout and still created a ticket. Two payments landing together could even take the number of issued tickets beyond the limit.</p>
<p>The merged fix now locks the event and rechecks the ticket sale, tier, expiry, and available capacity before creating anything. If an old order no longer qualifies, no ticket is issued and the case is logged for the team to handle manually. So yes, no more fun for y'all.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624dca9b375c2653317a1bc3/4cde5d79-46e6-413f-88ce-b81f784947f6.png" alt="Recommended payment fulfillment remediation" style="display:block;margin:0 auto" />

<p><em>Both payment-completion paths converge on one serialized validation step, followed by ticket creation or manual handling.</em></p>
<h2>What made this possible</h2>
<p>Three systems had related but distinct states:</p>
<table>
<thead>
<tr>
<th>System</th>
<th>State after midnight</th>
</tr>
</thead>
<tbody><tr>
<td>IndiaFOSS ticket UI</td>
<td>Late Bird was past <code>valid_till</code> and could no longer be selected</td>
</tr>
<tr>
<td>FOSS United payment record</td>
<td>The earlier ₹1500 order was still pending</td>
</tr>
<tr>
<td>Razorpay</td>
<td>The order remained open and payable</td>
</tr>
</tbody></table>
<p>The ticket-page deadline controlled whether a customer could create a new selection through the UI. It did not invalidate the payment order that had already been created.</p>
<p>Once I paid that order, the rest of the process remained valid:</p>
<ol>
<li><p>Razorpay charged the amount already attached to the order.</p>
</li>
<li><p>Razorpay returned a signed payment result.</p>
</li>
<li><p>FOSS United verified the signature with its private secret.</p>
</li>
<li><p>FOSS United marked the pending payment as captured.</p>
</li>
<li><p>The stored attendee metadata was used to issue the ticket.</p>
</li>
</ol>
<p>This was not a way to change the ticket price or create a ticket without payment. FOSS United's server-side amount calculation prevented the former, and Razorpay's signature verification prevented the latter.</p>
<p>It was an edge case in order expiry: the storefront stopped offering the tier before an already-created payment order stopped being usable.</p>
<h2>Lessons for payment integrations</h2>
<h3>Treat the server-calculated amount as authoritative</h3>
<p>The client can display a total, but the backend should derive the final amount from trusted products, tiers, discounts, and quantities. FOSS United did this correctly.</p>
<h3>Define what happens to pending orders at a deadline</h3>
<p>If a ticket tier expires at midnight, decide whether existing orders receive a grace period. If they should not remain payable, cancel or invalidate them and handle any delayed successful payments safely.</p>
<h3>Verify payment results before fulfillment</h3>
<p>A browser saying "payment succeeded" is not enough. Verify Razorpay's signature or consume a trusted webhook before creating tickets or entitlements.</p>
<h3>Make success endpoints idempotent</h3>
<p>Payment callbacks and webhooks can be retried. Repeating a valid callback for the same order should confirm the existing purchase, not create duplicate fulfillment.</p>
<h3>Return useful completion data</h3>
<p>An HTTP 200 with <code>{}</code> was valid but unclear. Returning a status and ticket identifier would make the integration easier to operate:</p>
<pre><code class="language-json">{
  "status": "captured",
  "ticket_id": "..."
}
</code></pre>
<h3>Keep callbacks and webhooks conceptually separate</h3>
<p>The browser callback improves the immediate user experience. The webhook provides server-to-server reliability if the browser closes or loses connectivity. A robust integration usually accounts for both.</p>
<h2>Where the coding agent helped</h2>
<p>The agent was useful for the mechanical parts of the investigation:</p>
<ul>
<li><p>querying the event API and reading ticket-tier data</p>
</li>
<li><p>locating the relevant FOSS United source files</p>
</li>
<li><p>generating the minimal HTML checkout page</p>
</li>
<li><p>making and inspecting HTTP requests</p>
</li>
<li><p>opening the success route and confirming the resulting ticket</p>
</li>
</ul>
<p>The important reasoning still came from understanding the payment model. I knew why an order ID could outlive the page that created it, why embedding a public key was normal, why the private secret must remain on FOSS United's server, and why the signed result still had to be verified.</p>
<p>That combination worked well: prior experience supplied the hypothesis, open source supplied the exact implementation, and the coding agent reduced the time needed to test it.</p>
<h2>Closing</h2>
<p>I started with a simple mistake: after four years of attending IndiaFOSS, I forgot to buy this year's ticket on time. But I learnt an important lesson: do not give up, and keep tinkering around. In the process, I learnt a few things and contributed to open source in its truest fashion.</p>
<p>I have hidden all personal details, PII, and IDs, and used generic screenshots to stop people from recreating this on the platform. I tried to make sure the organizers would not have a bad time dealing with any of that, and I waited until the PRs were merged too. Please be nice to them, okay?</p>
<p>If this was useful and you are interested in open source, payment systems, or the people building both, come to <strong>IndiaFOSS 2026</strong> in Bengaluru. I will be there.</p>
<p>Come say hi.</p>
<p>Anas Khan <a href="https://anaskhan.me">anaskhan.me</a></p>
]]></content:encoded></item><item><title><![CDATA[REST, GraphQL, or gRPC: Choosing the Right API Paradigm]]></title><description><![CDATA[The Problem
If you are new to backend engineering, the world of APIs can feel overwhelming. You learn REST because it is everywhere, the de facto standard for web services. Then you hear about GraphQL and how it solves all of REST's problems. Then so...]]></description><link>https://blog.anaskhan.me/api-paradigms</link><guid isPermaLink="true">https://blog.anaskhan.me/api-paradigms</guid><category><![CDATA[APIs]]></category><category><![CDATA[backend]]></category><category><![CDATA[REST API]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[gRPC]]></category><dc:creator><![CDATA[Anas Khan]]></dc:creator><pubDate>Tue, 07 Oct 2025 15:30:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759834488772/1b09d7da-7153-461c-9f96-62f769aa50ee.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-problem">The Problem</h2>
<p>If you are new to backend engineering, the world of APIs can feel overwhelming. You learn REST because it is everywhere, the de facto standard for web services. Then you hear about GraphQL and how it solves all of REST's problems. Then someone on your team mentions gRPC for a new microservice, talking about performance and protocol buffers. And then there is tRPC, which seems to be popular in the full-stack JavaScript world.</p>
<p>How do you decide what to use? This is not an academic question. The choice you make has long-term consequences for your system's performance, scalability, and the productivity of your team. Picking the wrong paradigm can lead to slow applications, bloated data transfers, tight coupling between teams, and a frustrating developer experience.</p>
<p>As engineers, we need to understand our tools deeply. This post will break down the core ideas behind REST, GraphQL, and gRPC. We will look at their strengths, weaknesses, and the specific problems they are designed to solve. By the end, you will have a clear mental model and a practical framework for choosing the right API paradigm for your next project.</p>
<h2 id="heading-core-concepts">Core Concepts</h2>
<p>An Application Programming Interface (API) is a contract that allows two pieces of software to communicate. When we talk about web APIs, we are usually referring to how a client (like a web browser or mobile app) communicates with a server over a network. Let's dissect the most common paradigms.</p>
<h3 id="heading-rest">REST</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760100207273/cdafb25f-6f73-4130-ad87-3d9ebe035a7c.png" alt /></p>
<p>REST stands for <strong>RE</strong>presentational <strong>S</strong>tate <strong>T</strong>ransfer. It is not a protocol or a library; it is an architectural style built on top of the HTTP protocol. It has been the dominant style for over a decade for a reason: it is simple, and it models the web itself.</p>
<p>The core idea of REST is to think about your system in terms of <strong>resources</strong>. A resource is any object or entity you want to expose, like a <code>User</code>, a <code>Product</code>, or an <code>Order</code>. Each resource has a unique identifier, which is its URL (Uniform Resource Locator).</p>
<p>You interact with these resources using standard HTTP methods, which map directly to CRUD (Create, Read, Update, Delete) operations:</p>
<ul>
<li><p><code>GET /users/1</code>: Retrieve the user with ID 1.</p>
</li>
<li><p><code>POST /users</code>: Create a new user.</p>
</li>
<li><p><code>PUT /users/1</code>: Update the <strong>entire</strong> user with ID 1.</p>
</li>
<li><p><code>PATCH /users/1</code>: Update <strong>part</strong> of the user with ID 1.</p>
</li>
<li><p><code>DELETE /users/1</code>: Delete the user with ID 1.</p>
</li>
</ul>
<p>A key constraint of REST is that it is <strong>stateless</strong>. Every request from a client to a server must contain all the information needed to understand and complete the request. The server does not store any client context between requests. This makes REST APIs highly scalable because any server can handle any request, which simplifies load balancing.</p>
<details>
  <summary>When to use PUT vs PATCH?</summary>
<p><code>PATCH</code> is often overlooked but important. While <code>PUT</code> replaces the entire resource, <code>PATCH</code> modifies only the fields you specify. For example, if you just want to update a user's email without changing their other data, <code>PATCH</code> is the correct choice. It leads to smaller payloads and more efficient updates in many real-world APIs.</p>

<img src="https://pbs.twimg.com/media/G0zWBF1WgAANSc-?format=png&amp;name=medium" alt="Image" />

<a target="_blank" href="https://x.com/waheed_hanzalah/status/1967182540931997707">Source: Hanzalah Waheed on X</a>

</details>

<p><strong>Real-world Example:</strong> Most websites use REST internally for service integration. For example, authentication, billing, and notifications services often communicate via REST APIs even inside large systems.</p>
<p><strong>Pros of REST:</strong></p>
<ul>
<li><p><strong>Simplicity and Standardization:</strong> REST is easy to understand and implement. The learning curve is low, and the vast majority of web developers are familiar with it.</p>
</li>
<li><p><strong>Leverages HTTP:</strong> It uses HTTP features, like caching for <code>GET</code> requests, to its full potential. Browsers, proxies, and CDNs can cache responses automatically, improving performance.</p>
</li>
<li><p><strong>Wide Adoption:</strong> The tooling and ecosystem around REST are mature and extensive. Libraries exist for every language, and tools like OpenAPI (Swagger) make documentation and code generation straightforward.</p>
</li>
<li><p><strong>Ideal for public APIs</strong> and <strong>service integration</strong> (e.g., Slack, Stripe, GitHub)</p>
</li>
</ul>
<p><strong>Cons of REST:</strong></p>
<p>REST's simplicity is also its limitation. Two major problems often surface in complex applications:</p>
<ol>
<li><p><strong>Over-fetching:</strong> The server defines the shape of the resource. When you request <code>/users/1</code>, you get the entire user object, which might include their name, email, address, creation date, and more. If your mobile app's homepage just needs to display the user's name, you are still fetching all the other data. This wastes bandwidth, which is critical for mobile users.</p>
</li>
<li><p><strong>Under-fetching and the N+1 Problem:</strong> Sometimes you need data from multiple resources. To show a user's profile and their last five orders, you first have to <code>GET /users/1</code>. Then, you have to make another request to <code>GET /users/1/orders?limit=5</code>. This requires multiple round trips to the server, which adds latency. It gets worse if you need to fetch a list of users and then fetch the orders for each user in that list (the N+1 query problem). These issues led to the development of GraphQL, which we will discuss next.</p>
</li>
</ol>
<h4 id="heading-example-rest-with-fastapi">Example: REST with FastAPI</h4>
<pre><code class="lang-bash">pip install <span class="hljs-string">"fastapi[standard]"</span>
</code></pre>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI
<span class="hljs-keyword">from</span> fastapi.responses <span class="hljs-keyword">import</span> JSONResponse

app = FastAPI()

users = {
    <span class="hljs-string">"1"</span>: {<span class="hljs-string">"name"</span>: <span class="hljs-string">"Anas Khan"</span>, <span class="hljs-string">"email"</span>: <span class="hljs-string">"anas@example.com"</span>},
    <span class="hljs-string">"2"</span>: {<span class="hljs-string">"name"</span>: <span class="hljs-string">"Ninad Naik"</span>, <span class="hljs-string">"email"</span>: <span class="hljs-string">"ninad@example.com"</span>},
}

<span class="hljs-meta">@app.get("/users/{user_id}")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user</span>(<span class="hljs-params">user_id: str</span>):</span>
    user = users.get(user_id)
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> user:
        <span class="hljs-keyword">return</span> JSONResponse({<span class="hljs-string">"error"</span>: <span class="hljs-string">"User not found"</span>}, status_code=<span class="hljs-number">404</span>)
    <span class="hljs-keyword">return</span> JSONResponse(user)
</code></pre>
<p><strong>Run:</strong></p>
<pre><code class="lang-bash">fastapi dev main.py
</code></pre>
<p><strong>Test:</strong></p>
<pre><code class="lang-bash">curl http://127.0.0.1:8000/users/1
</code></pre>
<p><strong>Expected Output:</strong></p>
<pre><code class="lang-plaintext">{"name": "Anas Khan", "email": "anas@example.com"}
</code></pre>
<h3 id="heading-graphql">GraphQL</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760100229985/2966e021-c9ef-47d2-8272-5371f41a594c.png" alt /></p>
<p>GraphQL is not an architectural style but a <strong>query language for your API</strong>. Developed by Facebook and open-sourced in 2015, it was created to solve the exact problems of over-fetching and under-fetching that their mobile teams were facing.</p>
<p>With GraphQL, the client has the power. Instead of having multiple endpoints for different resources, you typically have a single endpoint (e.g., <code>/graphql</code>). The client sends a <code>POST</code> request to this endpoint with a query that specifies exactly the data it needs, including nested relationships.</p>
<p>The server then returns a JSON object that mirrors the structure of the query.</p>
<p>Consider the user and orders example. With GraphQL, the client can get all the required data in a single request:</p>
<p>Example query:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">query</span> {
  user(<span class="hljs-symbol">id:</span> <span class="hljs-string">"1"</span>) {
    name
    email
  }
}
</code></pre>
<p>The server response will be a JSON object containing only the user's name and email. Nothing more, nothing less.</p>
<p>GraphQL is also <strong>strongly typed</strong>. You define the capabilities of your API in a schema using the GraphQL Schema Definition Language (SDL). This schema acts as a contract between the client and the server, and it enables powerful developer tools like auto-completion and static validation of queries.</p>
<p><strong>Real-world Example: LeetCode</strong> uses GraphQL. Open your browser's DevTools, switch to the <strong>Network</strong> tab, and filter for <code>/graphql</code>. You will see the queries being made to fetch problem lists, discussions, and submissions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759861711999/0bc5b61d-379a-4e6a-9a85-e98a536a3805.jpeg" alt="Screenshot of LeetCode API response using GraphQL" class="image--center mx-auto" /></p>
<p><strong>Pros of GraphQL:</strong></p>
<ul>
<li><p><strong>Efficient Data Fetching:</strong> It eliminates over-fetching and under-fetching, making it ideal for applications with complex data needs or for clients on slow networks, like mobile apps.</p>
</li>
<li><p><strong>Improved Developer Experience:</strong> Frontend developers can request the data they need without waiting for the backend team to create new endpoints. The schema provides excellent self-documentation.</p>
</li>
<li><p><strong>Evolving APIs without Versioning:</strong> You can add new fields to your schema without breaking existing clients. Clients only get the data they explicitly ask for, so new additions are ignored unless requested. You can deprecate old fields and use tooling to see which clients are still using them.</p>
</li>
</ul>
<p><strong>Cons of GraphQL:</strong></p>
<ul>
<li><p><strong>Complexity:</strong> GraphQL is more complex to set up and maintain than REST. You need to manage a schema, write resolvers (the functions that fetch the data for each field), and understand concepts like mutations (for writing data) and subscriptions (for real-time updates).</p>
</li>
<li><p><strong>Caching Challenges:</strong> Since most GraphQL queries are sent as <code>POST</code> requests to a single endpoint, you cannot leverage standard HTTP caching. Client-side caching libraries like Apollo Client or Relay are powerful but add their own layer of complexity.</p>
</li>
<li><p><strong>Potential for Abusive Queries:</strong> A malicious or poorly written client query could ask for many levels of nested data, triggering a massive database load. You need to implement safeguards like query depth limiting, timeouts, or cost analysis to prevent this.</p>
</li>
</ul>
<h4 id="heading-example-graphql-with-strawberry">Example: GraphQL with Strawberry</h4>
<pre><code class="lang-bash">pip install <span class="hljs-string">"fastapi[standard]"</span> <span class="hljs-string">"strawberry-graphql[fastapi]"</span>
</code></pre>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> strawberry
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI
<span class="hljs-keyword">from</span> strawberry.fastapi <span class="hljs-keyword">import</span> GraphQLRouter

<span class="hljs-meta">@strawberry.type</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>:</span>
    id: str
    name: str
    email: str

users = {
    <span class="hljs-string">"1"</span>: User(id=<span class="hljs-string">"1"</span>, name=<span class="hljs-string">"Anas Khan"</span>, email=<span class="hljs-string">"anas@example.com"</span>),
    <span class="hljs-string">"2"</span>: User(id=<span class="hljs-string">"2"</span>, name=<span class="hljs-string">"Ninad Naik"</span>, email=<span class="hljs-string">"ninad@example.com"</span>),
}

<span class="hljs-meta">@strawberry.type</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Query</span>:</span>
<span class="hljs-meta">    @strawberry.field</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">user</span>(<span class="hljs-params">self, id: str</span>) -&gt; User | <span class="hljs-keyword">None</span>:</span>
        <span class="hljs-keyword">return</span> users.get(id)

schema = strawberry.Schema(query=Query)

graphql_app = GraphQLRouter(schema)

app = FastAPI()
app.include_router(graphql_app, prefix=<span class="hljs-string">"/graphql"</span>)
</code></pre>
<p><strong>Run:</strong></p>
<pre><code class="lang-bash">fastapi dev main.py
</code></pre>
<p><strong>Test:</strong> Query:</p>
<pre><code class="lang-graphql">{
  user(<span class="hljs-symbol">id:</span> <span class="hljs-string">"2"</span>) {
    name
    email
  }
}
</code></pre>
<p>Send the query using curl:</p>
<pre><code class="lang-bash">curl -X POST \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{"query": "{ user(id: \"2\") { name email } }"}'</span> \
  http://127.0.0.1:8000/graphql
</code></pre>
<p><strong>Expected Output:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"data"</span>: {
    <span class="hljs-attr">"user"</span>: {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Ninad Naik"</span>,
      <span class="hljs-attr">"email"</span>: <span class="hljs-string">"ninad@example.com"</span>
    }
  }
}
</code></pre>
<h3 id="heading-grpc">gRPC</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760100249974/ddaf433b-8464-4f49-a48c-a1298b5df5f6.png" alt class="image--center mx-auto" /></p>
<p>gRPC stands for Google Remote Procedure Call. It is a modern, open-source framework that takes a different approach. While REST is about resources and GraphQL is about queries, gRPC is all about <strong>actions</strong>. It is based on the idea of a Remote Procedure Call (RPC), where a client can directly call a method on a server application on a different machine as if it were a local object.</p>
<p>With gRPC, you define your services and messages in a <code>.proto</code> file using <strong>Protocol Buffers (Protobuf)</strong>. Protobuf is Google's language-neutral, platform-neutral mechanism for serializing structured data. Think of it like JSON, but smaller, faster, and it generates native code for you.</p>
<p>Here is a simple Protobuf definition:</p>
<pre><code class="lang-java">syntax = <span class="hljs-string">"proto3"</span>;

service UserService {
  <span class="hljs-function">rpc <span class="hljs-title">GetUser</span><span class="hljs-params">(UserRequest)</span> <span class="hljs-title">returns</span> <span class="hljs-params">(UserResponse)</span></span>;
}

message UserRequest {
  string id = <span class="hljs-number">1</span>;
}

message UserResponse {
  string name = <span class="hljs-number">1</span>;
  string email = <span class="hljs-number">2</span>;
}
</code></pre>
<p>You use a Protobuf compiler (<code>protoc</code>) to generate client and server code in your language of choice (e.g., Python, Go, Java). The client then gets a "stub" that has the same methods as the server. When the client calls <code>GetUser()</code>, gRPC handles serializing the request into a compact binary format, sending it to the server, and deserializing the binary response.</p>
<p>gRPC is built on <strong>HTTP/2</strong>, which is a major upgrade over the HTTP/1.1 that most REST APIs use. HTTP/2 brings features like:</p>
<ul>
<li><p><strong>Multiplexing:</strong> Sending multiple requests and responses over a single TCP connection, reducing latency.</p>
</li>
<li><p><strong>Bi-directional Streaming:</strong> The client and server can send a stream of messages to each other independently and simultaneously. This is perfect for real-time applications.</p>
</li>
</ul>
<p><strong>Pros of gRPC:</strong></p>
<ul>
<li><p><strong>Bandwidth and Efficiency:</strong> The first and biggest advantage of gRPC is its use of <strong>Protocol Buffers</strong>, which are binary and much smaller than JSON. On average, gRPC reduces payload sizes by <strong>60–80%</strong> compared to traditional REST-based JSON APIs. It also minimizes <strong>network overhead</strong> through <strong>HTTP/2 multiplexing</strong>, which allows multiple requests and responses to share a single connection without the need for repeated handshakes. In large-scale microservice systems with continuous internal communication, these optimizations can save <strong>hundreds of megabytes per second</strong>. Because of this, gRPC is particularly well suited for <strong>performance-critical workloads</strong> such as financial trading platforms, IoT telemetry systems, and real-time streaming applications where low latency and high throughput are essential.</p>
</li>
<li><p><strong>Microservice Communication:</strong> This is gRPC's sweet spot. For internal, server-to-server communication where performance is critical, gRPC is unmatched. The low latency and high throughput make it ideal.</p>
</li>
<li><p><strong>Streaming:</strong> If you need real-time, bi-directional streaming for things like chat applications, live data feeds, or IoT devices, gRPC provides this out of the box.</p>
</li>
</ul>
<p><strong>Cons of gRPC:</strong></p>
<ul>
<li><p><strong>Limited Browser Support:</strong> You cannot call a gRPC service directly from a web browser. The browser does not give you the fine-grained control over HTTP/2 requests that gRPC requires. You need a proxy layer like gRPC-Web to translate requests.</p>
</li>
<li><p><strong>Less Human-Readable:</strong> The payload is a binary format. You cannot just use <code>curl</code> to inspect a request or response easily like you can with JSON. Debugging can be more challenging without the right tools.</p>
</li>
<li><p><strong>Steeper Learning Curve:</strong> It requires you to learn Protobuf, understand the code generation process, and work with a more rigid, contract-first approach.</p>
</li>
</ul>
<h4 id="heading-example-grpc-in-python">Example: gRPC in Python</h4>
<pre><code class="lang-bash">pip install grpcio grpcio-tools
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. user.proto
</code></pre>
<p>user.proto</p>
<pre><code class="lang-plaintext">syntax = "proto3";

service UserService {
  rpc GetUser(UserRequest) returns (UserResponse);
}

message UserRequest {
  string id = 1;
}

message UserResponse {
  string name = 1;
  string email = 2;
}
</code></pre>
<p>server.py</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> grpc
<span class="hljs-keyword">from</span> concurrent <span class="hljs-keyword">import</span> futures
<span class="hljs-keyword">import</span> user_pb2
<span class="hljs-keyword">import</span> user_pb2_grpc

users = {
    <span class="hljs-string">"1"</span>: {<span class="hljs-string">"name"</span>: <span class="hljs-string">"Anas Khan"</span>, <span class="hljs-string">"email"</span>: <span class="hljs-string">"anas@example.com"</span>},
    <span class="hljs-string">"2"</span>: {<span class="hljs-string">"name"</span>: <span class="hljs-string">"Ninad Naik"</span>, <span class="hljs-string">"email"</span>: <span class="hljs-string">"ninad@example.com"</span>}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span>(<span class="hljs-params">user_pb2_grpc.UserServiceServicer</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">GetUser</span>(<span class="hljs-params">self, request, context</span>):</span>
        user = users.get(request.id)
        <span class="hljs-keyword">if</span> user:
            <span class="hljs-keyword">return</span> user_pb2.UserResponse(name=user[<span class="hljs-string">"name"</span>], email=user[<span class="hljs-string">"email"</span>])
        context.abort(grpc.StatusCode.NOT_FOUND, <span class="hljs-string">"User not found"</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">serve</span>():</span>
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=<span class="hljs-number">10</span>))
    user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
    server.add_insecure_port(<span class="hljs-string">'[::]:50051'</span>)
    print(<span class="hljs-string">"Server running on port 50051"</span>)
    server.start()
    server.wait_for_termination()

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    serve()
</code></pre>
<p>client.py</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> grpc
<span class="hljs-keyword">import</span> user_pb2
<span class="hljs-keyword">import</span> user_pb2_grpc

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>():</span>
    <span class="hljs-keyword">with</span> grpc.insecure_channel(<span class="hljs-string">'localhost:50051'</span>) <span class="hljs-keyword">as</span> channel:
        stub = user_pb2_grpc.UserServiceStub(channel)

        response = stub.GetUser(user_pb2.UserRequest(id=<span class="hljs-string">"1"</span>))
        print(<span class="hljs-string">f"User: <span class="hljs-subst">{response.name}</span>, Email: <span class="hljs-subst">{response.email}</span>"</span>)

        response = stub.GetUser(user_pb2.UserRequest(id=<span class="hljs-string">"2"</span>))
        print(<span class="hljs-string">f"User: <span class="hljs-subst">{response.name}</span>, Email: <span class="hljs-subst">{response.email}</span>"</span>)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    run()
</code></pre>
<p>Run Server (terminal 1):</p>
<pre><code class="lang-bash">python server.py
</code></pre>
<p>Run Client (terminal 2):</p>
<pre><code class="lang-bash">python client.py
</code></pre>
<p>Output:</p>
<pre><code class="lang-bash">User: Anas Khan, Email: anas@example.com
User: Ninad Naik, Email: ninad@example.com
</code></pre>
<h3 id="heading-a-quick-note-on-trpc">A Quick Note on tRPC</h3>
<p>You might also hear about tRPC (TypeScript Remote Procedure Call). It is important to understand this is not a general-purpose paradigm like the others. tRPC is designed specifically for full-stack TypeScript applications, often in a monorepo. It allows you to share types between your backend and frontend, giving you end-to-end type safety. Your frontend can call backend functions as if they were local, with full auto-completion and type checking. It is extremely lightweight and offers an amazing developer experience, but it tightly couples your client and server and is only for the TypeScript ecosystem.</p>
<h2 id="heading-mental-model-ordering-food">Mental Model: Ordering Food</h2>
<p>To solidify these concepts, let's use an analogy: ordering food at a restaurant.</p>
<ul>
<li><p><strong>REST is like ordering from a fixed menu.</strong> You go to the counter and say "I want the number 5 combo." You get exactly what the menu describes for the number 5 combo: a burger, fries, and a drink. If you do not want the drink (over-fetching), too bad, it comes with the combo. If you want an extra side of onion rings (under-fetching), you have to make a separate order.</p>
</li>
<li><p><strong>GraphQL is like a custom buffet where you give the chef a specific list.</strong> You go to a single counter and hand them a detailed note: "I want a burger patty, two slices of cheddar cheese, a toasted bun, a handful of lettuce, and three onion rings." The chef gets you exactly that, all in one trip. You get precisely what you need, efficiently.</p>
</li>
<li><p><strong>gRPC is like the high-speed, internal communication system between the restaurant's kitchens.</strong> It is not for customers. The kitchens use a special, highly efficient shorthand (Protobuf) and a pneumatic tube system (HTTP/2) to send precisely measured ingredients (binary data) to each other instantly. It is all about speed and efficiency for internal operations.</p>
</li>
</ul>
<h2 id="heading-step-by-step-how-to-choose-your-paradigm">Step-by-Step: How to Choose Your Paradigm</h2>
<p>When starting a new project, ask yourself these questions to guide your decision.</p>
<p><strong>1. Who are the primary consumers of this API?</strong></p>
<ul>
<li><p><strong>Public / Third-Party Developers:</strong> Lean towards <strong>REST</strong>. Its simplicity, standardization, and vast ecosystem make it the easiest for external developers to adopt. The barrier to entry is low.</p>
</li>
<li><p><strong>Your Own Frontend Teams (Web &amp; Mobile):</strong> <strong>GraphQL</strong> is a very strong contender. It decouples the frontend from the backend, allowing them to iterate faster. The efficiency gains are especially valuable for mobile clients.</p>
</li>
<li><p><strong>Internal Microservices:</strong> <strong>gRPC</strong> is the default choice here. The performance, strict contracts, and streaming capabilities are perfect for server-to-server communication.</p>
</li>
<li><p><strong>A Tightly Coupled Full-Stack TypeScript App:</strong> Consider <strong>tRPC</strong> for an unbeatable developer experience and end-to-end type safety.</p>
</li>
</ul>
<p><strong>2. What does your data look like?</strong></p>
<ul>
<li><p><strong>Simple, well-defined resources (CRUD-heavy):</strong> <strong>REST</strong> is a natural fit. Its resource-based model works perfectly for this.</p>
</li>
<li><p><strong>Complex, nested, or graph-like data:</strong> <strong>GraphQL</strong> excels. A social media feed with users, posts, comments, and likes is a classic use case.</p>
</li>
<li><p><strong>Actions and Commands:</strong> If your API is more about performing actions (<code>archiveUser</code>, <code>calculateRisk</code>) than managing resources, an RPC-style API like <strong>gRPC</strong> feels more natural.</p>
</li>
</ul>
<p><strong>3. What are your performance and network requirements?</strong></p>
<ul>
<li><p><strong>Standard Web Traffic:</strong> <strong>REST</strong> and <strong>GraphQL</strong> are generally sufficient.</p>
</li>
<li><p><strong>Critical Low Latency &amp; High Throughput:</strong> <strong>gRPC</strong> is the clear winner. For applications like financial trading systems or real-time multiplayer game backends, every microsecond counts.</p>
</li>
<li><p><strong>Bandwidth Constrained Clients (Mobile/IoT):</strong> <strong>GraphQL</strong> provides fine-grained control to minimize data transfer. <strong>gRPC</strong> is also extremely efficient due to its binary format.</p>
</li>
</ul>
<h2 id="heading-trade-offs-and-considerations">Trade-offs and Considerations</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>REST</td><td>GraphQL</td><td>gRPC</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Paradigm</strong></td><td>Architectural Style (Resources)</td><td>Query Language</td><td>RPC Framework (Actions)</td></tr>
<tr>
<td><strong>Data Format</strong></td><td>JSON (most common), XML, etc.</td><td>JSON</td><td>Protocol Buffers (Binary)</td></tr>
<tr>
<td><strong>Transport</strong></td><td>HTTP/1.1, HTTP/2</td><td>HTTP/1.1, HTTP/2</td><td>HTTP/2</td></tr>
<tr>
<td><strong>Performance</strong></td><td>Good, but prone to payload issues</td><td>Excellent for data fetching efficiency</td><td>Highest performance, lowest latency</td></tr>
<tr>
<td><strong>Caching</strong></td><td>Excellent via standard HTTP caching</td><td>Complex, requires client-side libraries</td><td>Not supported by default, handled at app level</td></tr>
<tr>
<td><strong>Schema/Contract</strong></td><td>Optional (OpenAPI)</td><td>Required (Strongly Typed Schema)</td><td>Required (Strict .proto contract)</td></tr>
<tr>
<td><strong>Developer Experience</strong></td><td>Low learning curve, easy to start</td><td>Frontend freedom, powerful tooling</td><td>Contract-first, excellent for polyglot systems</td></tr>
<tr>
<td><strong>Best Use Case</strong></td><td>Public APIs, simple resource services</td><td>Mobile/Web frontends, complex data models</td><td>Internal microservices, streaming, performance</td></tr>
</tbody>
</table>
</div><h2 id="heading-key-takeaways">Key Takeaways</h2>
<p>There is no "best" API paradigm. The right choice depends entirely on the problem you are solving.</p>
<p>As a developers, our job is to understand the trade-offs. Analyze your requirements and then pick the right tool. Building scalable systems starts with making these foundational architectural decisions correctly.</p>
]]></content:encoded></item><item><title><![CDATA[Decoding Interview Coder]]></title><description><![CDATA[Note: My views are my own and don't reflect the opinion of my employer.
I'm a final-year CS engineering student, and like many of you, I've spent significant time preparing and attempting over 30+ online coding assessments on various programming webs...]]></description><link>https://blog.anaskhan.me/decoding-interview-coder</link><guid isPermaLink="true">https://blog.anaskhan.me/decoding-interview-coder</guid><category><![CDATA[Interview-coder]]></category><category><![CDATA[hiring]]></category><category><![CDATA[technology]]></category><category><![CDATA[vibe coding]]></category><dc:creator><![CDATA[Anas Khan]]></dc:creator><pubDate>Tue, 01 Apr 2025 16:00:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743523003002/2f1c18a7-6be0-4587-a1f5-c68a08409fb6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Note:</strong> <em>My views are my own and don't reflect the opinion of my employer.</em></p>
<p>I'm a final-year CS engineering student, and like many of you, I've spent <em>significant</em> time preparing and attempting over 30+ online coding assessments on various programming websites—HackerRank, HackerEarth, SHL, et al. Over time, I’ve discovered quite a few loopholes and exploits in these platforms and even reported some of them to the respective platforms.</p>
<p>Later, in a series of different events that deserve its own blog post (<em>coming soon.</em>) I ended up interning at HackerRank! The first thing I wanted to do here? You guessed it: to work on fixing those loopholes. It was fun while I knew the various exploits and how to leverage them to game the online assessment, but it also made me the ideal person to test the platform and fix these loopholes continuously. So far, I’ve analyzed and reported over a dozen vulnerabilities while at HackerRank. <a target="_blank" href="https://youtu.be/ApnXM7gxRrE?t=3920"><em>Shameless plug: I'm that intern!</em></a><br /><a target="_blank" href="https://youtu.be/ApnXM7gxRrE?list=PLLhBy6YSIT0DjPC-a9j-Alz21GHOjzrVe&amp;t=3920">  
</a>Lately, I started hearing the buzz around a tool called “<strong>Interview Coder</strong>”, promising to make cheating "<strong>undetectable</strong>”. I was more intrigued by the story of this 21-year-old dropout who made this tool. I immediately knew I had to check it out. So, I tested it out over a weekend on multiple online tests. Here’s what I found -</p>
<h2 id="heading-first-impressions-of-interview-coder"><strong>First Impressions of Interview Coder</strong></h2>
<p>The first thing you notice on Interview Coder’s website is the big “<strong>F*ck Leetcode</strong>” message. I thought that was pretty cheeky and frankly, I was glad that someone finally called it out given the fact that I’ve myself been very vocal about changing the traditional online assessments and have lobbied hard for having better assessments that allow the usage of AI and which simulate real-world problems instead of having candidates grind Leetcode-style problems.</p>
<p>However, it’s hard not to judge a tool explicitly designed to help people crack interviews while going to great lengths to stay hidden. Ethically, that doesn’t exactly fall into a “grey” area but rather a “pitch-black” area. Taking external help, whether AI or not, when the interview explicitly forbids it, is by definition, cheating. Just stating the obvious here, although I think neither the tool’s author nor the users have any disagreements or qualms about this. Yet, I’ve tried my best to be unbiased and evaluate this tool from the perspective of a candidate who wants to use this to cheat (for whatever reason) and see how effective or helpful it is.</p>
<h2 id="heading-taking-it-for-a-spin"><strong>Taking it for a spin</strong></h2>
<p>The installation process was relatively straightforward, requiring screen-sharing permissions. The interface seemed simple, with very few actions a user could take. I’m a big fan of minimalist design, so kudos on that. Almost all actions have keyboard shortcuts, which is also really helpful, especially to avoid raising suspicion in an interview. Pretty neat!</p>
<p>But then I noticed there was no "Forgot Password" flow! That’s strange. Even the most basic CRUD app tutorials cover this. If you happen to lose your hastily created password for the  $60/month subscription, you’ll have to reach out to the author and wait for their reply! Hmm, this already looks more like a college undergrad’s hack than an actual product.</p>
<p>The UI also doesn’t display which account you're currently logged into. While this is not a deal-breaker, it is another example of the overall lack of polish and attention to detail.</p>
<p>I found the layout to be not very user-friendly. Most of the time, it worked, but sometimes, I had to scroll not just vertically but also horizontally, even for relatively short code snippets.  A few times, I found it quite difficult to use, especially for Python one-liners, which are often truncated or require excessive scrolling. I accidentally discovered that zooming out on the overlay helps <em>slightly</em>, but you're still limited to viewing a certain number of lines.</p>
<p>Coming to the overlay, it's semi-transparent and you cannot even adjust it, it significantly blocks the view of the editor. I was forced to constantly toggle between the tool and the editor, or awkwardly position them side-by-side. This added a significant cognitive load, making it harder to focus on the <em>actual problem</em>.</p>
<p>At this point, I’m already quite disappointed by the tool. But then I realized, there's no obvious way to exit the app cleanly. This, combined with the fact that the tool hijacks your keyboard shortcuts, makes it difficult to resume normal work after the test.</p>
<p>Frequent updates further complicate things. Since the app isn’t visible in the dock, there's no obvious way to quit and update it, leaving you feeling stuck unless you manually kill the process.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXddyzXz_AhTS3QdOmyX3dFPI_I472CmjvO291WNgl2zgqoTYcDyf04cvcZQv7gtalqvDaYkzwCeXByTHo_lKi8AkY3KYrKmWuhUmGFM59NViKHI-D4H9w39Vj-TKuxfENT6NCGz?key=qDwvqLFqAx5Fp3SgGQoY5lWk" alt /></p>
<h2 id="heading-problem-solvability"><strong>Problem Solvability</strong></h2>
<p>The core of Interview Coder's functionality, the thing it's supposed to be <em>good</em> at, is solving coding problems. And in this, surprisingly it fails quite a lot. The reason is probably the fact that it is just a GPT wrapper, packaged in Electron, primarily utilizing (from the source code I have) Antrophic's Claude 3.5 Sonnet more specifically <code>claude-3-5-sonnet-20241022</code>. This means that any problem Claude struggles with, Interview Coder struggles with too.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe7LVHiGq6Uok-WmRdIi8WXAdAeOyIS9DdO-v4BSl4ITVZry7ZEe4rUDgs8wU-ZEut-RQBuEMFjgGzD7L2GkCd4q58v2RrdocMmbLijec_lR4mPd2txvIgQ5sfrJTMS5jSOv1zfmQ?key=qDwvqLFqAx5Fp3SgGQoY5lWk" alt /></p>
<p><em>System prompt and model found in the source code of webapp.</em></p>
<p>And that's a <em>lot</em> of problems in itself. Unseen questions, problems that deviate from the standard LeetCode ones, medium-to-hard problems involving trees or dynamic programming – these are all the areas where Interview Coder consistently falls short. In my testing, the only questions it reliably solved were those classic problems found in the LeetCode 150, which you would have probably known already if you were preparing for a technical interview. This is hardly surprising, given that LLMs are trained on vast datasets of  - you guessed it, <em>popular</em> solutions readily available online. As of now, the tool doesn’t support non-coding questions. API and System Design, etc. I have noticed a shift in hiring where OAs now include SQL and API questions, and the tool falls flat in those areas.</p>
<p>While it allows for uploading multiple screenshots, the application froze for me each time I tried this. This happened to me repeatedly, forcing me to restart. The "debug" feature, which is supposed to help you fix your code, seems similarly broken. During my testing, it consistently caused the application to freeze, forcing me to start over and get a completely <em>new</em> solution, which also often didn't work.</p>
<p>But here's the real kicker: even when it does manage to generate a "solution," it's often presented in such a cryptic, convoluted way that it's difficult to understand, let alone explain to an interviewer. The variable names are generic, the code style is textbook-bland, and there's a heavy reliance on list comprehensions and other techniques that scream "LLM-generated." I've seen plenty of human-written code and LLM-generated code. Any interviewer (or plagiarism detection mechanism) would immediately flag this code as AI-generated and highly suspicious. No questions asked.</p>
<h2 id="heading-so-is-it-really-worth-the-buck"><strong>So, is it really worth the buck?</strong></h2>
<p>Now, let's talk about the pricing and boy, is this where things go from bad to worse. Interview Coder charges a whopping <em>$60 per month</em> for 50 credits! I think $60 is exorbitant for what is essentially a glorified GPT wrapper, one that struggles with anything beyond the most basic, well-trodden LeetCode problems. You could get <em>far</em> more value (and significantly better performance) by directly using newer Claude or GPT models with your own API keys, all while costing you a few dollars at most. Heck, Gemini 2.5 Pro is <em>free</em> – why not allow users to leverage <em>that</em> instead of forcing them into your expensive, server-dependent ecosystem?</p>
<p>Considering the number of times the tool gives incorrect answers, the credit system starts to feel like an intentionally designed “dark pattern.” Each wrong answer still costs you credits.  You can burn through 10-12 credits in a single test, especially with restarts or the unreliable "debug" feature.</p>
<p>Additionally, there's absolutely <em>no</em> consumer protection:</p>
<ol>
<li><p>No Free Trial: You can download and install but can’t log in and solve any problems until you buy it. There's no way to test the tool's effectiveness before committing to the exorbitant monthly fee.</p>
</li>
<li><p>No Refund Policy: If the tool doesn’t work on your machine, you can’t request a refund. There’s no clear information on which machines or OS versions are supported. In my testing, I encountered different results across various OS builds.</p>
</li>
</ol>
<p>This doesn’t seem like a fair pricing model. Especially considering the fact that the users of this tool are primarily job seekers – often students – who are desperate to pass these interviews. It's particularly galling when you consider why it blew up because the initial builds of Interview Coder were open-source and allowed users to bring their own API keys. That approach made perfect sense: it eliminated the unnecessary server-side component, reduced costs, and gave users more control over their data and privacy. But somewhere along the way, the focus clearly shifted from providing a useful tool to maximizing ARR, resulting in this overpriced, underperforming mess.</p>
<h2 id="heading-security-issues"><strong>Security Issues:</strong></h2>
<p>But here's where things get really interesting. The tool isn't so much built as it is “vibe-coded”, mostly using Cursor AI. Don’t get me wrong. I have nothing against  “vibe-coding”. In fact, I do it a lot. But I do it mostly for building prototypes and quick hacks. I take comfort in the fact that if the auto-generated code doesn’t work, I’m fully capable of debugging and making it work.  I’ll never blindly push the auto-generated code to prod and hope it works. I certainly won’t be charging people for the hacks thus created until I’ve polished them into “production-quality products”.</p>
<p>This is where Interview Coder differs.  The security and authorization are lacking, to put it mildly. To demonstrate just how bad it is, <strong>I’ve attached a demo</strong> below – yes, the supposed $2M ARR project completely falls apart due to basic authentication failures. I can bypass the entire payment system with a few lines of code. This exploit should get patched, but I'm curious to see how long it takes the author to fix it.</p>
<p><strong>The Hack:</strong></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/hoHE4fkECss">https://youtu.be/hoHE4fkECss</a></div>
<p> </p>
<p>This isn't some sophisticated exploit; it was right there in plain sight. The commit history of the project is also interesting. The author attempted to make the repository private, but couldn't due to the 2k+ stars, so he resorted to rewriting the commit history. But, he forgot about branches, pretty novice. Traces of previous versions, including API keys, were still accessible.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcP1WLglY09Ai-TH9zZPjJExMhuay0Nr3qSS2PbsngJn0C-s5ncv66_u5yvsLmwCYFR0DXdBdv7yuk9cG55jRyLcMAEZj5FAeqefj4yep539dmkjnWG_aWDZWFjnv52m6ELwYeJCw?key=qDwvqLFqAx5Fp3SgGQoY5lWk" alt /></p>
<p><em>Other branches are still visible on the interview coder’s GitHub repo.</em></p>
<p>It's a clear demonstration of a lack of basic development skills, and security best practices. Earlier, the server component (<em>interview-coder-webapp</em>) was public too, and now it's not. With such major flaws, I find it hard to trust the tool with my systems’ screenshots that may contain screenshots of my interview with my face and sensitive information.</p>
<p>While I think it was a really cool idea and I would even have considered using the tool in some of my interviews, not because I think it’s ethically right, but just because I’m too bored to solve the problem myself, sadly it feels like a missed opportunity to build a reliable cheat tool.</p>
<h2 id="heading-the-real-cost-of-shortcuts"><strong>The Real Cost of Shortcuts</strong></h2>
<p>But the problem isn't Interview Coder; it's the <em>mindset</em>, the idea that there's a quick fix, a way to "game the system" without actually putting in the work. And that, my friends, is a dangerous illusion.</p>
<p>Let's be brutally honest: using a tool like Interview Coder, even if it <em>did</em> work perfectly (which it emphatically does not), is a short-sighted strategy. You might, just <em>might</em>, manage to squeeze through an interview. But what happens next? What happens when you're faced with real-world challenges, complex codebases, and the pressure of delivering actual results? You'll be exposed. You'll be overwhelmed. And you'll have cheated yourself out of the opportunity to actually <em>learn</em> and <em>grow</em>. Simply clearing an interview doesn’t amount to much. I’m aware of many companies who have immediately fired candidates once they figured they were not as good on the job as they were in the interview.</p>
<p>The tech interview process is flawed, yes. We all acknowledge that. But the answer isn't to find more sophisticated ways to cheat; it's to advocate for a <em>better system</em>. A system that values genuine problem-solving skills, collaboration, communication, and the ability to learn and adapt.</p>
<h2 id="heading-what-could-better-interviews-look-like"><strong>What could better interviews look like?</strong></h2>
<p>So, while I agree with Roy that Leetcode-style interviews need an upgrade and I personally would enjoy a different interview setting, what could such an interview look like?</p>
<p>I personally appreciate companies who take the effort to dig into my existing coding chops even before the interview happens. They look at my side projects, and my GitHub activity and talk to me about those. They try to understand what I’m really passionate about. I don’t mind take-home tests, in fact, I think take-homes are much better than live pair programming since they take off the artificial time pressure from interviews and allow me to code in my natural zone.</p>
<p>A good interviewer would give me messy data and ambiguous requirements rather than abstract puzzles. I think the best way for companies to gauge my instant value-add to their team would be to just give me their repo and ask me to fix an actual open bug.</p>
<p>The most enjoyable interviews I’ve had were the ones where the interviewer acted more like my partner in solving a complex problem and brainstormed different ideas with me to arrive at a solution. If you can collaborate and communicate well with the interviewer you can be somewhat sure that you’d work well with the rest of the hiring team too when you work with them.</p>
<h2 id="heading-to-roy-a-plea-for-responsibility"><strong>To Roy: A Plea for Responsibility</strong></h2>
<p>Roy, I won't sugarcoat it: Interview Coder is a bad product. It's poorly designed, not secure, and overpriced, and it promotes a fundamentally dishonest approach to the tech interview process. You're clearly a talented individual. You have the skills to build something genuinely useful, something that could actually <em>improve</em> the hiring landscape. Instead, you've created a tool that exacerbates the existing problems and preys on the anxieties of vulnerable students.</p>
<p>It's interesting to see the response to Interview Coder and similar tools from the community. New projects are emerging, aiming to detect and counteract these methods. It’s clear that this space is evolving rapidly. People want real solutions, not shortcuts that undermine the integrity of the interview process.</p>
<p>I urge you to reconsider your approach. Rethink, Interview Coder. Learn from this experience and use your skills to create something that improves the system rather than degrades it.</p>
<p>You've been pushing the narrative that the only way to crack interviews and land jobs at top companies is either two years of LeetCode practice or using your tool. I loved the marketing and the growth of it, but let’s not ignore the reality—your Columbia CS degree and <a target="_blank" href="https://leetcode.com/u/ibttf/">years of LeetCode</a> grinding played a huge role in your Big Tech offers. Selling the same dream to younger candidates is misleading, and frankly, harmful.</p>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>The tech interview process is a complex and often frustrating beast. But it's not insurmountable. And it's certainly not something to be "gamed" with shortcuts and cheating tools. The real key to success is to focus on building genuine skills, developing a deep understanding of fundamental concepts, and honing your ability to communicate and collaborate effectively. Invest your time and energy in becoming a <em>better</em> engineer. That's the only <em>real</em> shortcut to a successful career.</p>
<p>And as for me? I'll continue my work at HackerRank, and strive to create a fairer, more effective, and more meaningful assessment process (PS - y'all will see in some time). It's a long road, but it's a journey worth taking. Ultimately, we all benefit from a system that values genuine talent and integrity over the ability to memorize LeetCode solutions. Now, back to battling those exploits… and maybe finding a <em>proper</em> way to completely exit that darn Interview Coder app without restarting my machine.</p>
]]></content:encoded></item><item><title><![CDATA[My Atlan Interview Experience]]></title><description><![CDATA[Hello, everyone! Today, I’m sharing my experience interviewing for fellowship internship* at Atlan, a company I have admired for quite some time. It's been over a month since I received the offer letter, and I feel this is the perfect opportunity to ...]]></description><link>https://blog.anaskhan.me/atlan-interview-experience</link><guid isPermaLink="true">https://blog.anaskhan.me/atlan-interview-experience</guid><category><![CDATA[Atlan]]></category><category><![CDATA[internships]]></category><category><![CDATA[interview]]></category><category><![CDATA[Experience ]]></category><dc:creator><![CDATA[Anas Khan]]></dc:creator><pubDate>Mon, 09 Dec 2024 08:30:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733655690775/0dc19895-61dd-400d-965e-6fcbfe28fed9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello, everyone! Today, I’m sharing my experience interviewing for <s>fellowship</s> internship* at <strong>Atlan</strong>, a company I have admired for quite some time. It's been over a month since I received the offer letter, and I feel this is the perfect opportunity to walk you through my journey. My goal is to provide insights into the process, so if you’re planning to apply or just curious about what it's like, this blog might be just what you need.</p>
<p>Why am I sharing this? When I was preparing for the Software Engineering Internship role at Atlan, there weren’t many resources or detailed accounts available online. This gap inspired me to document my journey—both the highs and lows. So, without further ado, let’s dive in!</p>
<hr />
<h2 id="heading-how-it-began">How It Began</h2>
<p>Atlan’s internship wasn’t something I initially planned for. The opportunity was part of a program called the <strong>Atlan Engineering Fellowship</strong>, which they advertised on LinkedIn. I’d been following Atlan for some time because they recently had a Series C - funding round and when I looked at what it does I admired their work in data governance and engineering.</p>
<p>When I first came across the fellowship announcement, I was instantly intrigued. The application process was laid out neatly on their <a target="_blank" href="https://atlanhq.notion.site/Handbook-85beed963dee4b2abeece197636bd48f">Notion-powered website</a> (<em>which shows how cool and organized the company is)</em>.</p>
<p>Every step of the process was mentioned with absolute clarity. The program included a one-week in-person onboarding in Delhi, offering opportunities to network, gain a certificate, and possibly secure an internship.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733653382546/afcb6af3-78cb-41ba-bddb-2c4c248a9e92.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733653407530/3ab5f902-1a9d-4c6a-990a-3e89e0090cee.png" alt class="image--center mx-auto" /></p>
<p>The fellowship was scheduled for <strong>October</strong>, It sounded like a great opportunity, so I filled out the application form and even reached out for referrals. Unfortunately, I couldn’t secure any referrals, but I proceeded with my application.</p>
<hr />
<h2 id="heading-the-rejection-twice">The Rejection (Twice!)</h2>
<p>I applied in the <strong>first week of August</strong>. Shortly afterwards, I received an email on <strong>August 17th</strong> —a rejection. The email, though polite, was disappointing. I had spent time carefully crafting my responses, explaining why I wanted to work at Atlan, and sharing my experiences. Yet, they opted to move forward with other candidates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733653512253/4bc7966f-ad30-4822-b629-342c007e811d.png" alt class="image--center mx-auto" /></p>
<p>Then came something unusual. On <strong>September 1st</strong>, I received another rejection email—yes, twice! This second rejection left me confused but also a little amused. I had no idea what was coming next.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733653527338/215957af-1aba-4030-8f12-3b18836dd969.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-a-surprise-shortlist">A Surprise Shortlist</h2>
<p>On <strong>September 5th</strong>, out of nowhere, I received an email from <strong>CodeJudge</strong>, congratulating me on making it to the next round. I was initially unsure about what it meant, but upon digging deeper, I realized it was related to my Atlan application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733653560917/8ff3b355-5ee5-42e1-8085-e99f0b8f874d.png" alt class="image--center mx-auto" /></p>
<p>The round involved a 30-minute coding test to be done before <strong>September 8th</strong>. It consisted of a single LeetCode medium-level question. I quickly solved the problem using a brute solution and fortunately was able to optimise it for all test cases in time.</p>
<p>The following day, on <strong>September 6th</strong>, I received a detailed report from Atlan with my test results. This transparency was refreshing—it’s rare for companies to share test results with candidates.</p>
<hr />
<h2 id="heading-the-assignment">The Assignment:</h2>
<p>Soon after, I received an email from <a target="_blank" href="https://www.linkedin.com/in/nandakishoresubba/">Nanda</a>, Atlan’s Hiring Lead, informing me that they had received my challenge and were looking into it. On September 11th, I received an email from the team asking if I needed help with the challenge. The email suggested that I was still working on it and encouraged me to reach out with any questions. This left me puzzled because I had only completed the coding test and wasn’t aware of any additional assignments. Seeking clarification, I responded, explaining that I had only received the coding test.</p>
<p>The next day, on September 12th, I got a reply informing me that I was supposed to have received another assessment.</p>
<p>However, life happened. My college exams coincided with this challenge, and the project felt overwhelming because of the timeline provided. Considering the effort required for just a four-day fellowship, I chose to step back and informed Atlan that I couldn’t complete the assignment.</p>
<hr />
<h2 id="heading-the-assignment-part-2">The Assignment (Part 2):</h2>
<p>After all hopes were lost, I received an email from them regarding the fellowship program. They mentioned having candidates from various examinations and asked if I could provide my availability for the program. I filled out the provided Google form where I indicated my availability. This interaction gave me the impression that they were still organizing and figuring things out on their end. Nonetheless, I promptly responded with my availability for the internship.</p>
<hr />
<h2 id="heading-the-fellowship-evolves">The Fellowship Evolves</h2>
<p>A few days later, I received an exciting update: Atlan had restructured its program into a <strong>fellowship followed by a six-month internship</strong>, with the added benefit of full travel and accommodation for the onboarding week. This new format seemed incredibly appealing, and I was eager to dive in.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733653727341/b7599506-a593-4728-98b2-961457869915.png" alt class="image--center mx-auto" /></p>
<p>Excited about the opportunity, I reached out to request the assignment and got started right away. I dedicated all my time to designing, optimizing, and building the <strong>Personalized Travel Itinerary Generator</strong>.</p>
<p>I implemented a <strong>retrieval-augmented generation (RAG) model</strong> powered by <strong>Llama 3.2 LLM</strong>, ensuring the application delivered intelligent and dynamic itineraries tailored to user preferences. The RAG model had data from India’s top holiday destinations from a CSV file containing verified travel destination data sourced from <a target="_blank" href="https://www.kaggle.com/datasets/saketk511/travel-dataset-guide-to-indias-must-see-places">Kaggle</a>. <a target="_blank" href="https://www.kaggle.com/datasets/saketk511/travel-dataset-guide-to-indias-must-see-places">This</a> dataset has information about 300+ destinations, and user ratings, providing a rich source of information for generating personalized itineraries.</p>
<p>The process was meticulous—I focused on creating an intuitive <strong>frontend with React</strong> and <strong>backend using Python and FastAPI</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733653844375/82402602-9666-4900-8088-011b4a9302b6.png" alt class="image--center mx-auto" /></p>
<p><em>screenshot of my project landing page ^</em></p>
<p>I also prepared a comprehensive doc, detailing the application's architecture, decision-making process, and potential areas for future enhancement.</p>
<p>This project wasn’t just about meeting the requirements; it was about showcasing both creativity and technical skills. From design to deployment, I poured my best effort into making this application.</p>
<p>After completing the project, I submitted my work with high hopes. Given the effort and time I had invested, I eagerly awaited feedback.</p>
<p>However, <strong>days passed with no response</strong>. The lack of communication was unsettling. After dedicating so much to the project, the possibility of being ghosted was disheartening. I just wanted clarity, whether it was positive or negative.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733654160937/03bb9703-064b-46f4-9575-6920d6b222bc.png" alt class="image--center mx-auto" /></p>
<p><em>I reached out multiple times via email every day, hoping for a reply 🫠</em></p>
<p>Despite the delays, I stayed optimistic, knowing that the effort I put into the challenge would eventually pay off.</p>
<p>After several days of waiting, I finally received a response. They were happy with my assignment and wanted to move things forward. After a few calls to coordinate my availability, <strong>18th October</strong> was chosen for the next stage.</p>
<h2 id="heading-the-technical-interview-a-deep-dive">The Technical Interview: A Deep Dive</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733654302938/c61a0dd9-6884-49fb-980f-2103a434d6e7.png" alt class="image--center mx-auto" /></p>
<p>The first technical round was an intense one-hour session with Atlan’s senior engineers. The panel consisted of two engineers — one specializing in backend systems and the other in frontend development.</p>
<h3 id="heading-backend-discussion">Backend Discussion</h3>
<p>The interview started with a deep dive into my project. Interestingly, the interviewers hadn’t reviewed my submission beforehand, giving me the chance to explain everything in detail. They asked me questions about:</p>
<ul>
<li><p><strong>System design</strong>: How I’d optimize the project, improve functionality, and enhance scalability.</p>
</li>
<li><p><strong>Database</strong>: Why I chose a particular database, how I would manage rate-limiting, and ways to cache responses efficiently.</p>
</li>
<li><p><strong>APIs</strong>: The logic behind my API integrations and their implementations.</p>
</li>
</ul>
<p>While I confidently tackled most backend questions, I stumbled on a Redis-related query. My limited experience with Redis in production environments showed, but I owned up to it and did my best to answer.</p>
<h3 id="heading-frontend-discussion">Frontend Discussion</h3>
<p>By this time, 45 minutes had passed, and they knew I was strong on the backend, so I hinted that my frontend skills weren’t as strong. The frontend interview focused on improving user experience and my design philosophy. They asked how I’d change a few things, optimize for older browsers, and enhance accessibility. While I didn’t know about concepts like <strong>polyfills</strong> and <strong>Babel</strong> at the time, I made a note to research them later. The interviewer explained these concepts well to me.</p>
<p>Towards the end of the interview, we discussed work-life balance at Atlan, the remote nature of the job, and how my day would look like. I loved how interns and SDE 1s are given the same exposure, responsibilities, and opportunities to explore.</p>
<hr />
<h2 id="heading-the-managerial-round-a-candid-conversation">The Managerial Round: A Candid Conversation</h2>
<p>Nanda called to inform me that I had cleared the round, and he would like to schedule another half-hour interview.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733654390389/cade33e4-7b54-4d77-89a1-1e17bdfebcc4.png" alt class="image--center mx-auto" /></p>
<p>Later that day, at 5:30 PM, I had a second interview — a managerial round with one of Atlan’s senior leaders, who had been with the founders even before Atlan was established when they were working on a different product. This session, which lasted over an hour, was one of the most candid and intense interviews I’ve ever experienced.</p>
<p>The interviewer dug into my personal and professional background, asking about:</p>
<ul>
<li><p>My family, upbringing, and values.</p>
</li>
<li><p>My academic journey and internships.</p>
</li>
<li><p>Feedback from previous roles and how I handled challenges.</p>
</li>
<li><p>What I was hoping to gain from the internship at Atlan.</p>
</li>
</ul>
<p>This round was less about technical skills and more about understanding who I was as a person. While the feedback I received was constructive, it left me feeling unsure about my chances. It was the most intense managerial behaviour round I had ever experienced, mainly because I wasn’t fully prepared for it. I would highly advise preparing thoroughly for this round. I was caught off guard in this round.</p>
<hr />
<h2 id="heading-the-final-verdict">The Final Verdict</h2>
<p>After Friday, Saturday, and Sunday, <strong>each day felt like it would never end</strong>, and I was anxiously waiting.<br /><strong>Finally</strong>, on Monday morning, I received a call from Nanda, congratulating me on being <strong>SHORTLISTED FOR THE INTERNSHIP!!</strong> 🥳🎉</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733654540447/f3b6bc88-13f2-4f4b-ae6f-c4895de8b115.png" alt class="image--center mx-auto" /></p>
<p>The offer included the fellowship week in Delhi (November 4th–8th) and a six-month remote internship. I admire how Atlan, as a startup, constantly gathers feedback from its team members and acts upon it immediately. It’s not one of those situations where they say, "We'll consider it for the next batch"— they genuinely make changes right away, which is refreshing to see.</p>
<p>Reflecting on the entire experience, I have to say I’m impressed with Atlan. I love their website, the team, (<em>esp. cooperative hiring manager Nanda and kind interviewers</em>), and the fact that the company operates entirely remotely. Everything about Atlan resonates with me, from its innovative approach to its work culture. However, I had some personal commitments that prevented me from joining Atlan at that moment. While I’m disappointed that I couldn’t take part, I do not regret my decision—it was the right choice at the time. But if an opportunity arises again in the future, I would definitely consider working with Atlan. No hard feelings. This has been my fellowship and interview experience with Atlan.</p>
<hr />
<h2 id="heading-reflections-and-key-takeaways">Reflections and Key Takeaways</h2>
<p>Looking back, this journey was a mix of emotions—excitement, confusion, and ultimately, gratitude. Here are some key lessons for anyone preparing for Atlan or similar opportunities:</p>
<ul>
<li><p><strong>Persistence Pays Off</strong>: Despite initial rejections, staying engaged with the process opened new doors.</p>
</li>
<li><p><strong>Be Transparent</strong>: Communicate openly about challenges, such as deadlines or overlapping commitments.</p>
</li>
<li><p><strong>Skills Matter</strong>: They are looking for candidates with prior experience and those who have worked on relevant projects. If you lack that, come back more accomplished next time.</p>
</li>
<li><p><strong>Preparation is Key</strong>: Focus on both technical and behavioural aspects. Atlan values well-rounded candidates who can articulate their thought processes clearly.</p>
<hr />
</li>
</ul>
<h2 id="heading-fin">fin~</h2>
<p>If you’re considering applying, I encourage you to take the leap. Whether or not you make it, the process itself is a valuable opportunity to learn and grow. And who knows? It might just become the stepping stone to your next big adventure. At the very least, you’ll gain great experience and have an amazing story to tell, where all odds were against you.</p>
<p>Good luck, and feel free to reach out to me on <a target="_blank" href="https://www.linkedin.com/in/anxkhn">LinkedIn</a>.</p>
]]></content:encoded></item><item><title><![CDATA[General Mills Interview Experience]]></title><description><![CDATA[Hello everyone, my name is Anas Khan, and I'm a final-year student at TSEC. I recently completed a 2-month summer internship at General Mills and bagged a Pre-Placement Offer (PPO) too. I'm writing this blog to help you navigate the General Mills int...]]></description><link>https://blog.anaskhan.me/gm-interview</link><guid isPermaLink="true">https://blog.anaskhan.me/gm-interview</guid><category><![CDATA[general mills]]></category><category><![CDATA[Interview experience]]></category><category><![CDATA[internships]]></category><dc:creator><![CDATA[Anas Khan]]></dc:creator><pubDate>Fri, 13 Sep 2024 18:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759835487879/818fb7d9-96f5-4060-bc89-3a6e1e5db7b0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Hello everyone, my name is Anas Khan, and I'm a final-year student at TSEC. I recently completed a 2-month summer internship at General Mills and bagged a Pre-Placement Offer (PPO) too. I'm writing this blog to help you navigate the General Mills interview process.</strong></p>
<p>I had planned to write a blog about this topic, but my current internship limits my time online, so I've put together this document instead.</p>
<p>Let me provide some context: This document is meant to give you an overview of how the interview process and shortlisting worked. It’s a quick summary based on my experience, along with some tips that might help improve your chances. Your results may vary, of course. I put this together quickly and used some language models to refine it, so if you spot any mistakes, please let me know so I can fix them. I’ve included various numbers and details that might help you evaluate your chances better.</p>
<p>For a different perspective, you can also check out <a target="_blank" href="https://abhigyan.tech/blog/posts/general-mills-internship-1">Abhigyan's blog</a>. He received a PPO as well and has written extensively about his experience.</p>
<p>I've organised this document into two sections:</p>
<ul>
<li><p><strong>Gemini’s Version</strong>: This is a concise overview designed for a quick glance.</p>
</li>
<li><p><strong>The “10 Minute Version”</strong>: This section provides a more detailed, in-depth look with thorough and less polished information.</p>
</li>
</ul>
<h2 id="heading-geminis-version"><strong>Gemini’s Version:</strong></h2>
<p><strong>Timeline</strong></p>
<ul>
<li><p><strong>October 25th</strong>: General Mills formally reached out to our campus.</p>
</li>
<li><p><strong>November 9th</strong>: Pre-placement talk and online test.</p>
</li>
<li><p><strong>November 10th</strong>: Technical and Leadership Interviews for shortlisted candidates.</p>
</li>
</ul>
<p><strong>Day 1:</strong> Pre-Placement Talk <strong>and Online Assessment</strong></p>
<p><strong>Presentation</strong></p>
<ul>
<li><p><strong>My Experience</strong>: The Pre Placement Talk covered the company, its products, the Early Career Program (ECP), and the role after graduation.</p>
</li>
<li><p><strong>Actionable Items</strong>: Research the company, its products, and the ECP.</p>
</li>
<li><p><strong>Tips</strong>: Ask insightful questions during the Pre Placement Talk to demonstrate your interest and preparation.</p>
</li>
</ul>
<p><strong>Online Assessment (OA)</strong></p>
<ul>
<li><p><strong>My Experience</strong>: The OA was divided into technical and non-technical sections. The technical section focused on core computer science concepts, while the non-technical section covered logical reasoning, mathematics, and English grammar.</p>
</li>
<li><p><strong>Actionable Items</strong>:</p>
<ul>
<li><p><strong>Technical</strong>: Review core CS concepts, including OOPs, SQL, DBMS, OS, CN, and DSA.</p>
</li>
<li><p><strong>Non-technical</strong>: Practice aptitude questions on websites like IndiaBix.</p>
</li>
</ul>
</li>
<li><p><strong>Tips</strong>:</p>
<ul>
<li><p><strong>Manage your time effectively during the OA.</strong></p>
</li>
<li><p><strong>Don't spend too much time on any single question.</strong></p>
</li>
<li><p><strong>Make educated guesses if necessary.</strong></p>
</li>
</ul>
</li>
</ul>
<p><strong>Day 2: Interviews</strong></p>
<p><strong>Interview 1</strong></p>
<ul>
<li><p><strong>My Experience</strong>: This interview was heavily focused on technical and leadership skills. I was asked in-depth questions about my resume, projects, and technical knowledge.</p>
</li>
<li><p><strong>Actionable Items</strong>:</p>
<ul>
<li><p><strong>Know your resume inside out.</strong></p>
</li>
<li><p><strong>Be prepared to discuss your projects in detail, including the tech stack, architecture, and your contributions.</strong></p>
</li>
<li><p><strong>Review core CS concepts and be able to apply them to real-world scenarios.</strong></p>
</li>
<li><p><strong>Prepare examples of your leadership experiences.</strong></p>
</li>
</ul>
</li>
<li><p><strong>Tips</strong>:</p>
<ul>
<li><p><strong>Practice your communication skills.</strong></p>
</li>
<li><p><strong>Be confident and enthusiastic.</strong></p>
</li>
<li><p><strong>Demonstrate your passion for technology and your eagerness to learn.</strong></p>
</li>
</ul>
</li>
</ul>
<p>7 were shortlisted for the second round.</p>
<p><strong>Interview 2</strong></p>
<ul>
<li><p><strong>My Experience</strong>: This interview was conducted by a senior leader and HR. It focused on leadership, communication, and cultural fit. I was asked about my family background, interests, and ability to handle pressure.</p>
</li>
<li><p><strong>Actionable Items</strong>:</p>
<ul>
<li><p><strong>Prepare examples of your leadership and teamwork skills.</strong></p>
</li>
<li><p><strong>Articulate your career goals and how they align with General Mills' values.</strong></p>
</li>
<li><p><strong>Practice answering behavioural interview questions.</strong></p>
</li>
</ul>
</li>
<li><p><strong>Tips</strong>:</p>
<ul>
<li><p><strong>Be yourself and let your personality shine through.</strong></p>
</li>
<li><p><strong>Demonstrate your passion for the company and the role.</strong></p>
</li>
<li><p><strong>Ask thoughtful questions about the company culture and the team you'd be joining.</strong></p>
</li>
</ul>
</li>
</ul>
<p><strong>Results</strong></p>
<ul>
<li><p><strong>Out of over 300 applicants, only 2 were finally shortlisted and received offers.</strong></p>
</li>
<li><p><strong>The selection process is highly competitive, so don't be discouraged if you're not selected.</strong></p>
</li>
<li><p><strong>Learn from the experience and use it to improve your performance in future interviews.</strong></p>
</li>
</ul>
<p><strong>Words of Wisdom</strong></p>
<ul>
<li><p><strong>Don't give up! There are plenty of other great opportunities out there.</strong></p>
</li>
<li><p><strong>Statistically, it’s 1 in 150, a lot of you will go home disappointed, that’s part of growing up.</strong></p>
</li>
<li><p><strong>Keep learning and growing. The more you invest in yourself, the better your chances of success in the future.</strong></p>
</li>
</ul>
<h2 id="heading-the-10-minute-version"><strong>The “10 Minute Version”:</strong></h2>
<p>This section provides a more detailed, in-depth look with thorough and less polished information.</p>
<p>So starting off, as I can see General Mills reached out to us on campus last year on Oct 25th formally.</p>
<p>The email mentioned:</p>
<blockquote>
<p><strong>We are looking for:</strong></p>
<ul>
<li><p><strong>Strong analytical skills</strong></p>
</li>
<li><p><strong>Technical Domain flexibility</strong></p>
</li>
<li><p><strong>Learning agility</strong></p>
</li>
<li><p><strong>Communication skills</strong></p>
</li>
<li><p><strong>Ability to influence</strong></p>
</li>
</ul>
</blockquote>
<p>And I can say the entire shortlisting process was very much aligned in this direction. Continue reading on to know more.</p>
<p>On 9th November 2023 - the pre-placement talk and online test</p>
<ul>
<li><p><strong>Reporting Time: 1:30 pm Sharp</strong></p>
</li>
<li><p><strong>Pre Placement Talk to start at 1:30 pm</strong></p>
</li>
<li><p><strong>Test to start at 2:30 pm (College Campus)</strong></p>
</li>
</ul>
<p>10th November 2023 - Technical and Leadership Interviews for the shortlisted candidates</p>
<h3 id="heading-pre-placement-talk">Pre Placement Talk</h3>
<p>We started with a Pre-Placement Talk presentation. This covered what General Mills does and gave an overview of their Early Career Program (ECP), which is a new initiative similar to those at other Fortune 200 companies and common in Global Capability Centers (GCC).</p>
<p>We were part of the first pilot batch for this program. While the specifics of this program might not be relevant to you right now, I recommend paying close attention to the presentation. It will give you valuable insights into the company—its operations, the FMCG industry, its global impact, the countries it operates in, and its products. Most importantly, it will outline the ECP and what you can expect after graduation. This will help you understand whether the company and the program align with your career goals.</p>
<p>For me, it did make sense to get into a Fortune 200 and global leader in food company FMCG and I decided for summer it would be a good use of my time. During the presentation, I would encourage you to ask the right questions and how do you do that? Well, you should be prepared and have researched about what the company does. So a few videos on YouTube, a Wikipedia page of the company history, on top of it a few brands that they serve in the country and how they want to portray themselves on their official website. Once you see all of it, you'll get a better understanding and it will give you better judgement.</p>
<p>I didn’t focus on the stipend or other details because my research indicated that the overall opportunity was solid. The decision is yours.</p>
<p>Once the presentation talk is over, be ready for the fact that every round of the process is an elimination round. With over 300 people registered and shortlisted, we were given rooms for the subsequent stages. You’ll need to bring your laptop and complete the assignments on the Hire Pro platform within a college-proctored environment. Make sure you have the test link handy.</p>
<p>Carry your laptop for the exam; the lab setup will be communicated to you the day before. If you encounter issues like your Windows shutting down, battery problems, or pop-ups that disrupt your session, contact an in-charge immediately. Some candidates faced problems and left without seeking help, so it’s better to reach out and get your access reset if needed. This might be one of your first experiences with an online interview platform, and such technical issues can occur. Keep your laptop battery charged, disable notifications, and avoid changing tabs or engaging in any suspicious activity, as the platform is designed to detect and flag such actions.</p>
<h3 id="heading-online-assessment">Online Assessment</h3>
<p>The assessment is divided into two main sections: technical and non-technical.</p>
<p><strong>Technical Section:</strong> This part will focus on your core technical knowledge. While you’re unlikely to encounter coding problems, you should prepare for debugging problems, database management systems (DBMS), data structures and algorithms (DSA), computer networks (CN), operating systems (OS), object-oriented programming (OOP), and language syntax. Make sure you review these topics thoroughly.</p>
<p><strong>Non-Technical Section:</strong> This section will cover a range of areas including logical reasoning, mathematics, permutation and combination, English grammar, and brain teasers. You’ll also find standard problems like reading comprehension, family relations, and interest calculations. Practising these areas will help you tackle the variety of questions in this part of the assessment.</p>
<p>You can practice aptitude-type questions on websites like <a target="_blank" href="https://www.indiabix.com/">IndiaBix</a>, as recommended by my senior.</p>
<p>Tips :</p>
<p>For the technical section, having a solid grasp of the basics of a programming language is essential. If you’re familiar with C++ and understand pointers, memory addressing, and program compilation, you’ll be well-prepared. Additionally, ensure you’re comfortable with computer networks (CN), operating systems (OS), database management systems (DBMS), SQL, and data structures and algorithms (DSA).</p>
<p>Keep in mind that it’s impossible to master everything overnight. It’s helpful to have concise notes for quick reference. Some questions may be straightforward, like recalling a sorting algorithm, while others will require quick, critical thinking. This is where your knowledge will be most tested.</p>
<p>Time management is crucial during the assessment. If you’re stuck on a question, mark it and move on, then revisit it when you have more time or are able to solve it. If needed, make an educated guess. Given your background from JEE and CET, handling multiple-choice questions (MCQs) should be manageable.</p>
<p>In my experience, there were no coding-related DSA questions. I later learned from an ECP team member that this was intentional, and it made sense later for what they were looking for in a candidate.</p>
<p>Out of over 300 applicants, 29 were shortlisted for the next round.</p>
<p>Results were typically released by night, so be patient. The following day, you’ll be expected to come to campus for the interview rounds.</p>
<h3 id="heading-day-2-interviews">Day 2: Interviews</h3>
<h3 id="heading-first-interview-tech-leadership">First Interview: Tech + Leadership</h3>
<p>There were three panels, I was the first candidate interviewed on my panel, and my interview lasted nearly an hour. I faced two panellists who typed out everything I said, likely to evaluate and compare later. Their rapid typing might be a bit intimidating, but try to maintain eye contact with both of them to stay engaged.</p>
<p>The interview began with basic questions like “What do you do?” and “How are you?” and moved on to discuss the projects I had worked on. As the interview progressed, the questions became more standard and in-depth, covering various topics I will discuss next.</p>
<p>It was heavily focused on both technical skills and leadership qualities, as well as the attributes mentioned in the email—analytical skills, technology domain expertise, learning agility, communication skills, and leadership ability. They assessed exactly these parameters during the interview.</p>
<p>Here’s a golden rule of advice: know every detail of your resume inside out and be prepared to discuss each line for about ten minutes. For resume formatting, consider using popular templates like <a target="_blank" href="https://www.overleaf.com/latex/templates/deedy-cv/bjryvfsjdyxz">Deedy’s</a> or <a target="_blank" href="https://www.overleaf.com/latex/templates/jakes-resume/syzfjbzwjncs">Jake’s</a> on Overleaf. If you haven’t created your resume yet, make sure to write it concisely in bullet points.</p>
<p>Your resume should include descriptions of your internship and project experiences. Be prepared to discuss every aspect, including tech stacks, the reasons for choosing MongoDB over SQL or relational versus non-relational databases, OOPS and Data Structures implementation in your project, the EER diagram of your project, the architecture, and your specific contributions. If you worked on only the front end of a project, and understand the entire project thoroughly, you’ll be expected to talk about it. I’ve seen candidates get eliminated for not knowing basic details about projects they claimed to have worked on because they only worked on front end and the rest is a black box to them. Don’t be that person.</p>
<p>There are a few key areas you should focus on:</p>
<ol>
<li><p><strong>Object-Oriented Programming (OOP)</strong>: This is crucial, so make sure you’re comfortable with it.</p>
</li>
<li><p><strong>SQL</strong>: Understand joins, different types of indexes, and views. Be solid on basic DBMS concepts and writing SQL queries.</p>
</li>
<li><p><strong>Operating Systems</strong>: Know about scheduling algorithms and OS fundamentals.</p>
</li>
<li><p><strong>Computer Networks</strong>: Be familiar with the OSI 7-layer model, IPv4, IPv6, and how DNS works.</p>
</li>
<li><p><strong>Your Project</strong>: Know everything about the tech stack you used and the language, whether it’s compiled or interpreted, dynamic or static.</p>
</li>
</ol>
<p>In addition to showcasing your technical skills, your resume should also highlight your leadership experience. During my interview, I focused on this aspect in the second half by discussing my hackathon wins and my role as a team leader. This demonstrated my leadership abilities.</p>
<p>You should be ready to talk about your learning experiences, the challenges you faced during these events, and how you solved problems under pressure. They’re interested in how you handle complicated situations, what you built, and what you achieved. Expect questions that explore these HR-type topics, as they’re keen to see how you manage real-world problems and lead effectively.</p>
<p>When discussing your projects, it's important to convey more than just the technical details. You should explain the motivation behind creating the project, not simply that you used a particular technology because it was taught to you or because you're familiar with it.</p>
<p>For example, detail why you selected a particular technology stack over another, what alternatives you considered, and why you ultimately chose this. Describe the problem you aimed to solve and the impact of your solution. Discuss how you built the project, including handling auth, the database used and how you deployed the project. It’s also valuable to explain how the application is used and what you learned from the process. This depth of understanding demonstrates not just your technical skills but also your ability to think critically and make informed decisions.</p>
<p>One insight I can mention is that because General Mills is an MNC, they also have a lot of legacy code. So when people come with Gen AI and more and web3 stuff, they seem to be quite fascinated, but because those are experienced people, they can easily identify if you are bluffing or you know the deal, stick to basics, but right ones. If you know fancy stuff, make sure you understand it well. One feedback I got after the hiring was done was that our college students were good with tech stuff.</p>
<p>Interviews for others typically lasted between 30 to 40 minutes, which was a good sign that I had a chance of moving forward.</p>
<p>About 20 minutes after my interview, the results were announced and updated in real time. Some candidates were put on the waitlist, some were rejected, and many were shortlisted. The second round of interviews began just 20 minutes after the first round concluded. It all happened so quickly that I didn’t even have time to call my mom and share the news before the next stage began.</p>
<p>7 were shortlisted for the next round.</p>
<h3 id="heading-second-interview-senior-management-hr">Second Interview: Senior Management + HR</h3>
<p>The second round of interviews is conducted by a senior leader from the DnT (Digital and Technology) team, along with an HR representative. Expect a mix of HR and technical questions.</p>
<p>This round will delve into various aspects, everything from tech before and now including your family background, your 10th and 12th-grade results, and your interests outside of tech. They’ll also assess your ability to handle pressure, your learning agility, and your leadership skills. You can demonstrate these through your committee work, hackathon experiences, or any other relevant activities.</p>
<p>Be prepared to discuss a broad range of topics, not just in-depth knowledge in one area. For example, despite my experience being primarily in web development, I was asked about Flutter and app development because it was part of my curriculum. My second-round interview lasted about an hour. The results were announced about an hour later, and it was quite tense.</p>
<p>After the second round of interviews, they announced the results with a small celebration. There were banners, and everyone got some gifts and goodies. It felt like a special moment as they revealed the final selections. I was thrilled to be among the chosen ones, along with Abhigyan. It really felt like a victory after all the hard work.</p>
<h3 id="heading-some-more-stuff">Some more stuff:</h3>
<p>From what I’ve seen after talking to other interns at General Mills, the selection ratio remains consistent. Typically, around 2 or 3 people are shortlisted from each campus, and I expect that trend to continue.</p>
<p>For actionable advice, focus on improving your communication skills. While that’s something that takes time, you can prepare for standard questions right away. Practice your responses to questions about your introduction, hobbies, experiences, technical skills, projects, and leadership roles. Rehearse in front of a mirror, do mock interviews with friends, and reach out to others who have been through the process—they might offer valuable insights.</p>
<p>General Mills focused on key attributes in their hiring process, as outlined in their job description: strong analytical skills, technical domain flexibility, learning agility, communication skills, and the ability to influence. They weren't just looking for top competitive programmers; instead, they valued candidates who could make an impact, lead effectively, and contribute to a positive work culture. As a People-First organization, General Mills emphasizes these critical qualities over technical skills alone, recognizing that while technical abilities can be developed, behaviours and interpersonal skills are fundamental to their culture and success.</p>
<p>Believe in yourself and stay positive. Even if things don’t work out this time, remember there are always other opportunities, and they might even turn out to be better. For example, I was rooting for a guy who didn’t make it but ended up landing a fantastic position elsewhere. You never know what might come your way.</p>
<p>The key is to give it your best shot, learn from the experience, and prepare yourself for future interviews. Even if you make mistakes or feel like you didn’t perform well, it’s okay. Each experience will teach you something and better prepare you for the next opportunity. So stay diligent, work hard, and remember that every step in the process is valuable.</p>
<h3 id="heading-fin">fin ~</h3>
<p>Lastly, if you have any questions, need further advice, or want to discuss anything that I might have missed in this blog, feel free to reach out to me on <a target="_blank" href="https://linkedin.com/in/anxkhn">LinkedIn</a> or <a target="_blank" href="https://x.com/anxkhn">Twitter</a>. Whether you need a 1:1 connect, a resume review, or just some additional insights, I'm here to help. I found that connecting with others and seeking help was beneficial in my journey, and I encourage you to do the same. Don’t hesitate to get in touch—I’m more than happy to help over the weekend!</p>
]]></content:encoded></item></channel></rss>