Beyond `requests.get`: Why a Simple API Call Reveals Your Engineering Depth

Junior/Mid Engineer Asked at: All modern tech companies

Q: Show how you would use Python to send a GET request to an API and print the response status code.

Why this matters: This is the "hello, world" of microservices and automation. But its simplicity is a deception. They aren't testing your ability to type `requests.get()`. They're testing your awareness of the most common failure point in any distributed system: the network.

Interview frequency: Guaranteed. A building block for countless engineering tasks.

❌ The Death Trap

The trap is assuming the network is reliable and the API is perfect. The candidate writes a single, optimistic line of code that will crash their application the moment reality intervenes.

"The most common—and most dangerous—answer is:"

import requests

response = requests.get('https://api.example.com/data')
print(response.status_code)

This code is a landmine. It will raise an unhandled exception and crash if there's a DNS failure, a network timeout, a connection error, or if the server hangs. This isn't just bad code; it's a denial of how distributed systems actually work.

🔄 The Reframe

What they're really asking: "How do you build a reliable bridge between your system and another? Show me you can handle the inevitable failures of that bridge with grace and intelligence."

This reveals if you practice defensive programming. Modern software is a mesh of conversations between services. Your answer demonstrates whether you can build a polite, resilient conversationalist, or one that hangs up and crashes at the first sign of a bad connection.

🧠 The Mental Model

I use the **"Polite Phone Call"** model for any network request.

1. Know Who You're Calling: Have the correct URL and any necessary headers or authentication.
2. Don't Wait Forever: Always use a `timeout`. You wouldn't wait on hold for an hour; your code shouldn't either.
3. Prepare for a Bad Connection: Make the call inside a `try...except` block. Be ready for the line to be busy (`ConnectionError`) or for them to not pick up (`Timeout`).
4. Check the Answer: Once they pick up, check the `status_code`. 200 is "Hello, here's what you wanted." 404 is "Sorry, wrong number." 500 is "Hold on, our office is on fire."

📖 The War Story

Situation: "We built a nightly cron job to sync user data from a third-party marketing API. The script was simple: fetch the data, do some processing, and save it to our database."

Challenge: "The engineer wrote the brittle, one-line version of the API call. It worked for months. Then, one Tuesday, the marketing API had a 30-minute outage right when our job was scheduled to run. Their server stopped accepting connections."

Stakes: "Our script, instead of handling the connection error, crashed with an unhandled `requests.exceptions.ConnectionError`. Because it crashed, the rest of the sync job—including critical cleanup and state-updating steps—never ran. This left our database in an inconsistent state, which took a senior engineer half a day to manually reconcile. A 30-minute external outage became a 4-hour internal incident because of one unprotected line of code."

✅ The Answer

"My approach is to treat any network call as an operation that can, and will, eventually fail. The code must be prepared for this reality from the start. I'd use the excellent `requests` library and wrap the call in robust error handling."

The Robust Solution

import requests
import sys

def get_api_status(url: str, timeout_seconds: int = 5):
    """
    Makes a GET request to a URL and prints its status code.
    Includes robust error handling for network issues and bad responses.
    """
    print(f"Attempting to reach {url}...")
    
    # The "Polite Phone Call" model in action
    try:
        # 1. Make the call, but don't wait forever (timeout)
        response = requests.get(url, timeout=timeout_seconds)

        # 2. Check for obvious client/server errors (4xx/5xx)
        response.raise_for_status()

    # 3. Handle bad connections (DNS failure, connection refused, etc.)
    except requests.exceptions.RequestException as e:
        print(f"Error: Could not connect to the API. {e}", file=sys.stderr)
        return None
    
    # 4. If the call was successful, print the good news
    print(f"Success! Response Status Code: {response.status_code}")
    return response.status_code

if __name__ == "__main__":
    get_api_status('https://api.github.com')  # A reliable API
    get_api_status('https://api.github.com/this-does-not-exist') # Will trigger a 404
    get_api_status('http://localhost:9999') # Will trigger a connection error

Key Improvements:

  • Timeout: The `timeout=5` is the most critical addition. It prevents the script from hanging indefinitely and tying up resources.
  • Exception Handling: Wrapping the call in `try...except requests.exceptions.RequestException` catches a whole family of network-related problems gracefully.
  • Status Code Check: `response.raise_for_status()` is a convenient way to automatically raise an exception if the status code is a 4xx (client error) or 5xx (server error), turning bad responses into explicit failures that can be caught.
  • Clarity: The function returns the status code on success and `None` on failure, providing a clean interface for other parts of a program to use.

🎯 The Memorable Hook

This reframes the problem as one of risk management. It shows you think about contracts and failure modes, not just code execution. It's a statement of engineering maturity.

💭 Inevitable Follow-ups

Q: "How would you pass an API key for authentication?"

Be ready: "The standard way is to use the `headers` parameter. I'd add `headers={'Authorization': f'Bearer {api_key}'}` to the `requests.get()` call. The key itself should be loaded securely from an environment variable or secrets manager, never hardcoded."

Q: "What if the API you're calling is occasionally flaky? How would you make your script more resilient?"

Be ready: "For transient errors like a `503 Service Unavailable`, I'd implement a retry mechanism with exponential backoff. Instead of failing immediately, the script would wait 1 second, then 2, then 4, and try again a few times before giving up. A great library for this is `tenacity`."

🔄 Adapt This Framework

If you're junior: The robust solution with `try/except` and a `timeout` is a perfect answer. It demonstrates a huge leap in maturity over the naive approach.

If you're senior: You should present the robust solution as your baseline, and then proactively discuss the follow-up topics. "Here's the basic safe way to do it. In a real production system, I'd immediately add retry logic with exponential backoff for 5xx errors and structured logging to capture the context of any failures."

Written by Benito J D