A complete, minimal Flask API serving a trained model's predictions — Flask's simplicity makes it a common first choice for wrapping a scikit-learn model in an HTTP interface.
Full Implementation
from flask import Flask, request, jsonify
import joblib
import numpy as np
app = Flask(__name__)
# Load the model ONCE, at startup -- not per-request (see ML Inference)
model = joblib.load("model_pipeline.pkl")
MODEL_VERSION = "1.2.0"
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "ok", "model_version": MODEL_VERSION})
@app.route("/predict", methods=["POST"])
def predict():
data = request.get_json()
# Basic input validation
required_fields = ["income", "age", "credit_score"]
missing = [f for f in required_fields if f not in data]
if missing:
return jsonify({"error": f"Missing fields: {missing}"}), 400
try:
features = np.array([[data["income"], data["age"], data["credit_score"]]])
prediction = model.predict(features)[0]
probability = model.predict_proba(features)[0].max()
except Exception as e:
return jsonify({"error": str(e)}), 500
return jsonify({
"prediction": str(prediction),
"probability": round(float(probability), 4),
"model_version": MODEL_VERSION,
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Testing the API
# From a terminal, using curl:
# curl -X POST http://localhost:5000/predict \
# -H "Content-Type: application/json" \
# -d '{"income": 45000, "age": 34, "credit_score": 680}'
import requests
response = requests.post(
"http://localhost:5000/predict",
json={"income": 45000, "age": 34, "credit_score": 680},
)
print(response.json())
Why Loading the Model at Module Level Matters
Loading model = joblib.load(...) at the top of the file — outside any request handler — means it happens exactly once, when the server starts, and every subsequent request reuses the already-loaded model in memory. Loading it inside the predict() function would reload the (potentially large) model file from disk on every single request, adding significant, unnecessary latency.
Practical Use Cases
- Simple, low-to-moderate traffic ML services where Flask's minimal setup is sufficient
- Internal tools and prototypes where FastAPI's extra structure (typed validation, async) isn't yet needed
Common Mistakes
- Loading the model inside the request handler instead of once at startup — a serious, easy-to-make latency bug.
- Skipping input validation, letting a malformed request crash the whole process with an unhandled exception.
- Running Flask's built-in development server in production — it's single-threaded and not designed for production traffic; use a proper WSGI server (like Gunicorn) instead.
Interview Relevance
Q: "Why shouldn't you use Flask's built-in app.run() server in production?" It's a lightweight development server, single-threaded by default, and not built to handle production-level concurrent traffic reliably or efficiently — production Flask deployments typically run behind a proper WSGI server like Gunicorn, often further behind a reverse proxy like Nginx.
Practice Question
Modify the Flask API above to also return a 400 error if credit_score is outside a plausible range (e.g. 300-850).