Framework vs SDK: Who Calls WhomCode & Development

Code & Development · 23 Aug 2026

Framework vs SDK: Who Calls Whom

Most developers memorize names, but few understand the mechanism. Here is the simple rule to tell a framework from an SDK and level up your debugging.

Genildo Souza23 Aug 2026Read: 10 min

Every week someone asks me some version of this:

Is Express a framework or a library?

Is the OpenAI SDK a framework?

Should I learn frameworks or SDKs first?

It sounds like a beginner question, but the confusion sticks around well into year three of a career. And it is not for lack of studying — it is that almost nobody explains the mechanism behind either one. People memorize names instead.

Once you understand the mechanism, something useful happens: you read docs faster, debug with less guesswork, and pick tools without depending on a Twitter thread. Let us get into it.

The starting point: who calls whom

Three flows side by side — your code calling a library, a framework calling your code, an SDK reaching the outside world.
One question separates all three: who is calling whom?

Hold on to this sentence, because it settles about 80% of the confusion:

You call the library. The framework calls you. The SDK talks to the outside world.

It sounds reductive. It is not. It is literally about your program''s flow of control — who is holding the steering wheel during execution.

Library: you are in charge

Diagram of your code calling a library function and receiving the return value.
You import it, you call it, execution returns to you. No "action," no movement.

A library is a bag of ready-made functions. You import it, call it when you feel like it, and execution comes right back to you.

javascript
import { format } from 'date-fns';

const today = format(new Date(), 'MM/dd/yyyy');
console.log(today); // your code is still driving
python
from datetime import datetime

today = datetime.now().strftime('%m/%d/%Y')
print(today)  # your code is still driving

You are the director. The library is the actor who steps in when you yell action. If you never call it, it does nothing.

Framework: it is in charge (and that is the whole point)

Your code at the center, surrounded by a framework ring with arrows pointing inward.
The Hollywood Principle in code: "don't call us, we'll call you."

Here everything flips. With a framework, you do not call its code — it calls yours.

This has a name: Inversion of Control (IoC), also known as the Hollywood Principle: do not call us, we will call you.

javascript
import express from 'express';
const app = express();

// You are NOT executing anything here.
// You are REGISTERING a function and saying:
// "when a GET hits /users, call this"
app.get('/users', (req, res) => {
  res.json([{ id: 1, name: 'Ana' }]);
});

app.listen(3000); // from this line on, the framework is in control
python
from fastapi import FastAPI
app = FastAPI()

# Same idea: the decorator registers your function with the framework.
# It decides when the function runs, not you.
@app.get("/users")
def list_users():
    return [{"id": 1, "name": "Ana"}]

Notice what you did not write in either snippet:

  • you did not open a TCP socket

  • you did not parse raw HTTP text

  • you did not build the response headers

  • you did not write the infinite loop waiting for connections

  • you did not handle concurrent requests

All of that exists and is running right now. It just lives inside the framework.

What a framework really does under the hood

Circular three-stage diagram: register, bootstrap and execution loop.
Register, bootstrap, execution loop — the three-beat skeleton every framework runs on.

Every framework — web, UI, testing, whatever — runs on the same three-beat skeleton.

1. Registration phase. You declare your pieces: routes, components, middleware, jobs, test cases. The framework stores them in an internal structure. app.get(...) does not execute your function; it pushes a record onto a list.

2. Bootstrap phase. You hand over control (app.listen(), uvicorn main:app, npm test). The framework assembles what it needs: server, dependency injection, connections, component tree.

3. Execution loop. The framework runs forever, listening for events. When one arrives, it consults that list from step 1, finds your function, and calls it — handing you pre-chewed data (req, res, props, event).

Here is crude pseudocode of what Express is doing while you sleep:

javascript
// this is the framework, not your code
while (true) {
  const rawRequest = await socket.acceptConnection();
  const req = parseHttp(rawRequest);            // becomes an object
  const route = routingTable.find(req.method, req.url);

  if (!route) return respond404();

  for (const middleware of middlewares) {       // lifecycle
    await middleware(req, res);
  }

  await route.handler(req, res);                // <- YOUR code, at last
}

This is why frameworks have a lifecycle (useEffect, beforeEach, middleware, onMount). Those are hooks the framework offers so you can inject code at specific moments of the loop it controls. You do not get to choose when your code runs — only where it hangs.

It is also why frameworks are opinionated. By letting one drive, you accept its folder structure, its naming, its way of doing things. In exchange you get speed, and you do not rewrite HTTP parsing for the thousandth time in the history of computing.

SDK: the translator between your code and someone else''s system

Your code connecting through an SDK block to a third-party service cloud.
A translator between your application and a distant system, so you never speak raw HTTP.

SDK stands for Software Development Kit. In practice, today, nearly every SDK is the same thing: a package that wraps a service''s API so you do not have to speak raw HTTP.

Without an SDK, integrating with a service looks like this:

python
import uuid
import requests

response = requests.post(
    "https://api.example.com/v1/charges",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"amount": 5000, "currency": "usd"},
    timeout=30,
)

if response.status_code == 429:
    ...  # now what? backoff? how many retries?
if response.status_code >= 500:
    ...  # retry? did the charge already go through?

data = response.json()  # untyped dict, no autocomplete

With an SDK:

python
from stripe import StripeClient

client = StripeClient(api_key=api_key)
charge = client.payment_intents.create({
    "amount": 5000,
    "currency": "usd",
})
print(charge.id)
javascript
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const charge = await stripe.paymentIntents.create({
  amount: 5000,
  currency: 'usd',
});
console.log(charge.id);

Three header lines and all the error handling vanished. But they did not stop existing — they just moved.

What a serious SDK handles for you

Six icons with labels for the work an SDK handles on your behalf.
Auth, retries, idempotency, pagination, typed errors. Boring code someone already battle-tested.
  • Authentication. Builds the header, refreshes expired tokens, reads the environment variable.

  • Serialization. Turns objects into JSON and JSON back into typed objects. You get autocomplete, which is underrated until you lose it.

  • Retry with exponential backoff. Got a 429 or a 503? Once you enable it, the client retries quickly on the first failure and then on an exponential backoff schedule, instead of giving up. Note it is opt-in: Stripe ships the mechanism, you configure it.

  • Idempotency. Generates a unique key so a retry does not charge your customer twice — Stripe stores that key for 24 hours and replays the original result. This one item alone pays for the SDK.

  • Pagination. Turns cursor juggling into a plain iterator.

  • Typed errors. RateLimitError, AuthenticationError — instead of comparing loose status numbers.

  • Versioning. Pins the remote API version so the server does not shift under your feet.

None of this is magic. It is boring, tedious, edge-case-riddled code that somebody already wrote, battle-tested in production, and maintains for you. That is the SDK.

The table worth taping to your monitor

Table comparing library, framework and SDK across seven traits.
The row that matters most is the highlighted one: cost to switch.

Trait

Library

Framework

SDK

Who calls whom

you call it

it calls you

you call it

Flow of control

yours

its

yours

Problem it solves

one specific task

your app structure

talking to an external system

Where the work runs

your machine

your machine

someone else''s server

How many per project

dozens

1 (rarely 2)

one per service

Cost of switching

low

brutal (rewrite)

medium

Examples

lodash, date-fns, pandas

React, Django, Rails, Spring

Stripe, AWS boto3, OpenAI

The most practical row is the cost of switching. Swapping date-fns for day.js is an afternoon. Swapping Django for FastAPI is a quarter. That is why framework choices deserve a meeting and library choices do not.

In real life, all three live in the same file

Code snippet with three annotations pointing at the framework, the SDK and the library.
Four lines, three roles. The framework is the stage, the SDK is the phone, the library is the workbench tool.

And there is nothing wrong with that:

python
import os

from fastapi import FastAPI          # framework: runs the application
from stripe import StripeClient      # SDK: talks to Stripe
from datetime import datetime        # library: one specific task

app = FastAPI()
stripe = StripeClient(api_key=os.environ["STRIPE_KEY"])

@app.post("/subscribe")                       # the FRAMEWORK will call this
def subscribe(amount: int):
    charge = stripe.payment_intents.create({  # YOU call the SDK
        "amount": amount,
        "currency": "usd",
    })
    return {
        "id": charge.id,
        "created_at": datetime.now().isoformat(),  # YOU call the library
    }

Three import lines, three different roles. The framework is the stage. The SDK is the phone. The library is the tool on the workbench.

What changes in your head after this

Two diagnostics side by side — a stack trace under a magnifier, and a network timeline under another.
Framework error? Look inward. SDK error? Look outward.

You debug better. A bug in framework code is almost never the framework''s fault — it is you using a hook at the wrong moment of the lifecycle. A bug in SDK code is almost always network, credentials, or rate limits. Knowing that cuts your investigation time in half.

You read docs in the right order. Framework docs: start with the lifecycle and the folder structure. SDK docs: start with auth and the error codes. Library docs: go straight to the function signature.

You choose with actual criteria. A framework is a marriage — check the community, the maintenance, the release cadence. An SDK is a vendor — prefer the official one, verify it has retries built in, and see how long since the last commit.

The rule of thumb

Decision tree leading from "does my code call this?" to framework, library or SDK.
Facing any unfamiliar package, just walk this path.

Facing any unfamiliar package, ask one question:

Does my code call this, or will this call my code?

The three answers:

  • You call it and the work happens on your machine — library.

  • You call it and the work happens on another company''s server — SDK.

  • It calls you and dictates your project structure — framework.

That is it. Nothing else to memorize.


If this was useful, the natural next step is to open the source of whatever framework you use most and go find the main loop. Seeing with your own eyes the exact moment it calls your function is one of those things you cannot unsee — and it is what separates people who use tools from people who understand them.


Sources

The framework definition Fowler cites comes from Ralph Johnson and Brian Foote: the methods defined by the user to tailor the framework will often be called from within the framework itself, rather than from the user''s application code. That sentence is from 1988 — the idea in this article is older than most of the tools it uses as examples.