Skeleton scripts
The blank-page problem kills more hobby projects than any technical wall does. Pick a language where it matters — most of these ship in more than one. Free to take, no strings, no sign-up.
Servo Sweep & Position Control
Get a servo moving predictably before you touch your real project.
View skeleton script
#include <Servo.h>
// ── CONFIG ────────────────────────────────
const int SERVO_PIN = 9; // TODO: your PWM-capable pin
const int MIN_ANGLE = 0;
const int MAX_ANGLE = 180;
const int STEP_DELAY = 15; // ms between steps
Servo myServo;
int angle = MIN_ANGLE;
int direction = 1;
void setup() {
myServo.attach(SERVO_PIN);
myServo.write(angle);
}
void loop() {
myServo.write(angle);
angle += direction;
if (angle >= MAX_ANGLE || angle <= MIN_ANGLE) {
direction *= -1;
}
delay(STEP_DELAY);
// TODO: replace the sweep with your real logic
}
void moveTo(int targetAngle) {
targetAngle = constrain(targetAngle, MIN_ANGLE, MAX_ANGLE);
myServo.write(targetAngle);
}
import time
import board
import pwmio
from adafruit_motor import servo
# ── CONFIG ────────────────────────────────
SERVO_PIN = board.D9 # TODO: your PWM-capable pin
MIN_ANGLE = 0
MAX_ANGLE = 180
STEP_DELAY = 0.015
pwm = pwmio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50)
my_servo = servo.Servo(pwm)
angle = MIN_ANGLE
direction = 1
def move_to(target_angle):
target_angle = max(MIN_ANGLE, min(MAX_ANGLE, target_angle))
my_servo.angle = target_angle
while True:
my_servo.angle = angle
angle += direction
if angle >= MAX_ANGLE or angle <= MIN_ANGLE:
direction *= -1
time.sleep(STEP_DELAY)
# TODO: replace the sweep with your real logic
DC Motor — PWM Driver
Direction and speed control through a standard H-bridge (L298N / TB6612-style).
View skeleton script
// ── CONFIG ────────────────────────────────
const int PIN_IN1 = 5; // TODO: H-bridge input 1
const int PIN_IN2 = 6; // TODO: H-bridge input 2
const int PIN_PWM = 9; // TODO: enable / speed pin
void setup() {
pinMode(PIN_IN1, OUTPUT);
pinMode(PIN_IN2, OUTPUT);
pinMode(PIN_PWM, OUTPUT);
stopMotor();
}
void loop() {
// TODO: replace this demo pattern with real control logic
forward(180); delay(1500);
stopMotor(); delay(400);
backward(180); delay(1500);
stopMotor(); delay(400);
}
void forward(int speed) {
digitalWrite(PIN_IN1, HIGH);
digitalWrite(PIN_IN2, LOW);
analogWrite(PIN_PWM, constrain(speed, 0, 255));
}
void backward(int speed) {
digitalWrite(PIN_IN1, LOW);
digitalWrite(PIN_IN2, HIGH);
analogWrite(PIN_PWM, constrain(speed, 0, 255));
}
void stopMotor() {
digitalWrite(PIN_IN1, LOW);
digitalWrite(PIN_IN2, LOW);
analogWrite(PIN_PWM, 0);
}
import RPi.GPIO as GPIO
import time
# ── CONFIG ────────────────────────────────
PIN_IN1 = 17
PIN_IN2 = 27
PIN_PWM = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_IN1, GPIO.OUT)
GPIO.setup(PIN_IN2, GPIO.OUT)
GPIO.setup(PIN_PWM, GPIO.OUT)
pwm = GPIO.PWM(PIN_PWM, 1000)
pwm.start(0)
def forward(speed):
GPIO.output(PIN_IN1, GPIO.HIGH)
GPIO.output(PIN_IN2, GPIO.LOW)
pwm.ChangeDutyCycle(max(0, min(100, speed)))
def backward(speed):
GPIO.output(PIN_IN1, GPIO.LOW)
GPIO.output(PIN_IN2, GPIO.HIGH)
pwm.ChangeDutyCycle(max(0, min(100, speed)))
def stop_motor():
GPIO.output(PIN_IN1, GPIO.LOW)
GPIO.output(PIN_IN2, GPIO.LOW)
pwm.ChangeDutyCycle(0)
try:
# TODO: replace this demo pattern with real control logic
while True:
forward(70); time.sleep(1.5)
stop_motor(); time.sleep(0.4)
backward(70); time.sleep(1.5)
stop_motor(); time.sleep(0.4)
except KeyboardInterrupt:
pwm.stop()
GPIO.cleanup()
Addressable LED Pattern
A rotating rainbow across a NeoPixel/WS2812 strip — swap the pattern math for your own.
View skeleton script
#include <FastLED.h>
// ── CONFIG ────────────────────────────────
#define LED_PIN 6 // TODO: your data pin
#define NUM_LEDS 30
#define BRIGHTNESS 80
CRGB leds[NUM_LEDS];
void setup() {
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
}
void loop() {
// TODO: replace with your real pattern
static uint8_t hue = 0;
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV(hue + (i * 256 / NUM_LEDS), 255, 255);
}
FastLED.show();
hue++;
delay(20);
}
import board
import neopixel
import time
# ── CONFIG ────────────────────────────────
LED_PIN = board.D6
NUM_LEDS = 30
BRIGHTNESS = 0.3
pixels = neopixel.NeoPixel(LED_PIN, NUM_LEDS, brightness=BRIGHTNESS, auto_write=False)
def wheel(pos):
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
elif pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
else:
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
hue = 0
while True:
# TODO: replace with your real pattern
for i in range(NUM_LEDS):
pixel_index = (i * 256 // NUM_LEDS) + hue
pixels[i] = wheel(pixel_index & 255)
pixels.show()
hue = (hue + 1) % 256
time.sleep(0.02)
Temperature & Humidity Sensor Reader
Poll a DHT22 on an interval and hand off clean readings, with bad-read retries baked in.
View skeleton script
#include <DHT.h>
// ── CONFIG ────────────────────────────────
#define DHT_PIN 2 // TODO: your data pin
#define DHT_TYPE DHT22
const unsigned long READ_INTERVAL = 2000; // ms, DHT22 min ~2s
DHT dht(DHT_PIN, DHT_TYPE);
unsigned long lastRead = 0;
void setup() {
Serial.begin(9600);
dht.begin();
}
void loop() {
if (millis() - lastRead >= READ_INTERVAL) {
lastRead = millis();
readSensor();
}
// TODO: replace the Serial print with your real logic
}
void readSensor() {
float humidity = dht.readHumidity();
float tempC = dht.readTemperature();
if (isnan(humidity) || isnan(tempC)) {
Serial.println("Read failed, will retry next interval");
return;
}
Serial.print("Temp: "); Serial.print(tempC);
Serial.print("C Humidity: "); Serial.print(humidity); Serial.println("%");
}
import time
import board
import adafruit_dht
# ── CONFIG ────────────────────────────────
DHT_PIN = board.D2 # TODO: your data pin
READ_INTERVAL = 2.0 # seconds, DHT22 min ~2s
dht = adafruit_dht.DHT22(DHT_PIN)
def read_sensor():
try:
temp_c = dht.temperature
humidity = dht.humidity
print(f"Temp: {temp_c}C Humidity: {humidity}%")
return temp_c, humidity
except RuntimeError as e:
# DHT sensors drop reads often — this is normal, just retry
print(f"Read failed ({e.args[0]}), will retry next interval")
return None, None
while True:
read_sensor() # TODO: replace with your real logic
time.sleep(READ_INTERVAL)
Button Input & Debounce
Clean single-press detection from a noisy mechanical switch — no phantom double-fires.
View skeleton script
// ── CONFIG ────────────────────────────────
const int BUTTON_PIN = 2; // TODO: your input pin
const unsigned long DEBOUNCE_MS = 40;
int lastReading = HIGH;
int stableState = HIGH;
unsigned long lastChangeTime = 0;
void setup() {
Serial.begin(9600);
pinMode(BUTTON_PIN, INPUT_PULLUP); // wired to GND when pressed
}
void loop() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) {
lastChangeTime = millis();
}
if (millis() - lastChangeTime > DEBOUNCE_MS) {
if (reading != stableState) {
stableState = reading;
if (stableState == LOW) {
onPress(); // TODO: replace with your real logic
}
}
}
lastReading = reading;
}
void onPress() {
Serial.println("Button pressed");
}
import RPi.GPIO as GPIO
import time
# ── CONFIG ────────────────────────────────
BUTTON_PIN = 17 # TODO: your input pin
DEBOUNCE_MS = 200
def on_press(channel):
# TODO: replace with your real logic
print("Button pressed")
GPIO.setmode(GPIO.BCM)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING,
callback=on_press, bouncetime=DEBOUNCE_MS)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup()
Web App Starter, No Build Step
State, render, and event handling in one file — open it and go.
View skeleton script
<!-- index.html — open directly in a browser, no server needed -->
<div id="app"></div>
<script>
// ── STATE ─────────────────────────────────
const state = { items: [] }; // TODO: shape this to your data
// ── RENDER ────────────────────────────────
function render() {
const app = document.getElementById('app');
app.innerHTML = `
<h1>My App</h1>
<button data-action="add">Add item</button>
<ul>
${state.items.map((item, i) =>
`<li>${item} <button data-action="remove" data-index="${i}">x</button></li>`
).join('')}
</ul>
`;
}
// ── EVENTS (delegated — survives re-renders) ─
document.getElementById('app').addEventListener('click', (e) => {
const action = e.target.dataset.action;
if (action === 'add') {
state.items.push(`Item ${state.items.length + 1}`);
render();
}
if (action === 'remove') {
state.items.splice(Number(e.target.dataset.index), 1);
render();
}
});
render();
</script>
from flask import Flask, render_template_string, request, jsonify
app = Flask(__name__)
# ── STATE ─────────────────────────────────
state = {"items": []} # TODO: shape this to your data
PAGE = """
<h1>My App</h1>
<button onclick="addItem()">Add item</button>
<ul id="list"></ul>
<script>
async function refresh() {
const res = await fetch('/items');
const data = await res.json();
document.getElementById('list').innerHTML =
data.items.map((item, i) => `<li>${item} <button onclick="removeItem(${i})">x</button></li>`).join('');
}
async function addItem() { await fetch('/items', { method: 'POST' }); refresh(); }
async function removeItem(i) { await fetch(`/items/${i}`, { method: 'DELETE' }); refresh(); }
refresh();
</script>
"""
@app.route("/")
def index():
return render_template_string(PAGE)
@app.route("/items", methods=["GET", "POST"])
def items():
if request.method == "POST":
state["items"].append(f"Item {len(state['items']) + 1}")
return jsonify(state)
@app.route("/items/<int:i>", methods=["DELETE"])
def remove_item(i):
if 0 <= i < len(state["items"]):
state["items"].pop(i)
return jsonify(state)
if __name__ == "__main__":
app.run(debug=True)
Debounced Live Search / Filter
Filters a list as the user types without hammering re-renders on every keystroke.
View skeleton script
<!-- index.html — open directly in a browser, no server needed -->
<input id="search" placeholder="Search..." />
<ul id="results"></ul>
<script>
// ── DATA ──────────────────────────────────
const items = ["Apple", "Banana", "Cherry"]; // TODO: your real data
// ── DEBOUNCE HELPER ───────────────────────
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
function render(list) {
document.getElementById('results').innerHTML =
list.map(item => `<li>${item}</li>`).join('');
}
const onSearch = debounce((query) => {
const filtered = items.filter(i =>
i.toLowerCase().includes(query.toLowerCase())
);
render(filtered); // TODO: replace with your real filtering logic
}, 200);
document.getElementById('search').addEventListener('input', (e) => {
onSearch(e.target.value);
});
render(items);
</script>
from flask import Flask, render_template_string, request, jsonify
app = Flask(__name__)
# ── DATA ──────────────────────────────────
ITEMS = ["Apple", "Banana", "Cherry"] # TODO: your real data
PAGE = """
<input id="search" placeholder="Search..." />
<ul id="results"></ul>
<script>
let timer;
document.getElementById('search').addEventListener('input', (e) => {
clearTimeout(timer);
timer = setTimeout(() => search(e.target.value), 200);
});
async function search(query) {
const res = await fetch(`/search?q=${encodeURIComponent(query)}`);
const data = await res.json();
document.getElementById('results').innerHTML =
data.items.map(i => `<li>${i}</li>`).join('');
}
search('');
</script>
"""
@app.route("/")
def index():
return render_template_string(PAGE)
@app.route("/search")
def search():
query = request.args.get("q", "").lower()
matches = [i for i in ITEMS if query in i.lower()] # TODO: replace with your real logic
return jsonify({"items": matches})
if __name__ == "__main__":
app.run(debug=True)
API Data Fetcher — Loading & Error States
The three-state fetch pattern every UI needs: loading, error, and success, handled once.
View skeleton script
<!-- index.html — open directly in a browser, no server needed -->
<div id="app"></div>
<script>
// ── CONFIG ────────────────────────────────
const API_URL = "https://api.example.com/data"; // TODO: your endpoint
const state = { status: "loading", data: null, error: null };
function render() {
const app = document.getElementById('app');
if (state.status === "loading") {
app.innerHTML = "<p>Loading...</p>";
} else if (state.status === "error") {
app.innerHTML = `<p>Something went wrong: ${state.error}</p>`;
} else {
// TODO: replace with your real rendering
app.innerHTML = `<pre>${JSON.stringify(state.data, null, 2)}</pre>`;
}
}
async function load() {
state.status = "loading";
render();
try {
const res = await fetch(API_URL);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
state.data = await res.json();
state.status = "success";
} catch (err) {
state.error = err.message;
state.status = "error";
}
render();
}
load();
</script>
import requests
import time
# ── CONFIG ────────────────────────────────
API_URL = "https://api.example.com/data" # TODO: your endpoint
MAX_RETRIES = 3
RETRY_DELAY = 1.5 # seconds
def load(url):
last_error = None
for attempt in range(1, MAX_RETRIES + 1):
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
last_error = e
print(f"Attempt {attempt} failed: {e}")
time.sleep(RETRY_DELAY)
raise RuntimeError(f"Giving up after {MAX_RETRIES} attempts: {last_error}")
if __name__ == "__main__":
try:
data = load(API_URL)
print(data) # TODO: replace with your real handling
except RuntimeError as e:
print(f"Error: {e}")
REST API Starter
Basic CRUD endpoints wired up and ready to point at a real data store.
View skeleton script
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
# ── MODEL ─────────────────────────────────
class Item(BaseModel):
name: str # TODO: shape this to your data
db: list[Item] = []
@app.get("/items")
def list_items():
return db
@app.post("/items")
def create_item(item: Item):
db.append(item)
return item
@app.get("/items/{item_id}")
def get_item(item_id: int):
if item_id >= len(db):
raise HTTPException(status_code=404, detail="Not found")
return db[item_id]
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
if item_id >= len(db):
raise HTTPException(status_code=404, detail="Not found")
return db.pop(item_id)
# Run with: uvicorn main:app --reload
const express = require('express');
const app = express();
app.use(express.json());
// ── STATE ─────────────────────────────────
let items = []; // TODO: shape this to your data
app.get('/items', (req, res) => {
res.json(items);
});
app.post('/items', (req, res) => {
const item = req.body; // TODO: validate
items.push(item);
res.status(201).json(item);
});
app.get('/items/:id', (req, res) => {
const item = items[req.params.id];
if (!item) return res.status(404).json({ error: 'Not found' });
res.json(item);
});
app.delete('/items/:id', (req, res) => {
if (!items[req.params.id]) return res.status(404).json({ error: 'Not found' });
const [removed] = items.splice(req.params.id, 1);
res.json(removed);
});
app.listen(3000, () => console.log('Listening on :3000'));
Realtime WebSocket Server
Broadcasts every message to all connected clients — the base for chat, multiplayer, or live dashboards.
View skeleton script
import asyncio
import websockets
# ── STATE ─────────────────────────────────
clients = set()
async def handler(websocket):
clients.add(websocket)
try:
async for message in websocket:
# TODO: replace with your real message handling
for client in clients:
if client != websocket:
await client.send(message)
finally:
clients.remove(websocket)
async def main():
async with websockets.serve(handler, "0.0.0.0", 8765):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8765 });
// ── STATE ─────────────────────────────────
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('message', (data) => {
// TODO: replace with your real message handling
for (const client of clients) {
if (client !== ws) client.send(data.toString());
}
});
ws.on('close', () => clients.delete(ws));
});
console.log('WebSocket server on :8765');
SQLite Persistence Layer
A tiny data layer with a real table and connection handling, so you stop losing state on restart.
View skeleton script
import sqlite3
# ── CONFIG ────────────────────────────────
DB_PATH = "app.db" # TODO: your database file
def get_connection():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
with get_connection() as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)
""") # TODO: shape this to your data
def add_item(name):
with get_connection() as conn:
cur = conn.execute("INSERT INTO items (name) VALUES (?)", (name,))
return cur.lastrowid
def list_items():
with get_connection() as conn:
rows = conn.execute("SELECT * FROM items").fetchall()
return [dict(row) for row in rows]
def delete_item(item_id):
with get_connection() as conn:
conn.execute("DELETE FROM items WHERE id = ?", (item_id,))
if __name__ == "__main__":
init_db()
add_item("Example") # TODO: replace with your real logic
print(list_items())
const Database = require('better-sqlite3');
// ── CONFIG ────────────────────────────────
const DB_PATH = 'app.db'; // TODO: your database file
const db = new Database(DB_PATH);
function initDb() {
db.exec(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)
`); // TODO: shape this to your data
}
function addItem(name) {
const stmt = db.prepare('INSERT INTO items (name) VALUES (?)');
const info = stmt.run(name);
return info.lastInsertRowid;
}
function listItems() {
return db.prepare('SELECT * FROM items').all();
}
function deleteItem(id) {
db.prepare('DELETE FROM items WHERE id = ?').run(id);
}
initDb();
addItem('Example'); // TODO: replace with your real logic
console.log(listItems());
File Watcher & Automation
Watches a folder and fires your own logic on any create, edit, or delete.
View skeleton script
import time
import os
# ── CONFIG ────────────────────────────────
WATCH_DIR = "./watched" # TODO: folder to watch
POLL_SECONDS = 2
def on_change(filename, event):
# TODO: replace with your real action
print(f"[{event}] {filename}")
def watch():
os.makedirs(WATCH_DIR, exist_ok=True)
seen = {f: os.path.getmtime(os.path.join(WATCH_DIR, f))
for f in os.listdir(WATCH_DIR)}
while True:
time.sleep(POLL_SECONDS)
current = os.listdir(WATCH_DIR)
for f in current:
path = os.path.join(WATCH_DIR, f)
mtime = os.path.getmtime(path)
if f not in seen:
on_change(f, "created")
elif mtime != seen[f]:
on_change(f, "modified")
seen[f] = mtime
for f in list(seen):
if f not in current:
on_change(f, "deleted")
del seen[f]
if __name__ == "__main__":
watch()
const fs = require('fs');
const path = require('path');
// ── CONFIG ────────────────────────────────
const WATCH_DIR = './watched'; // TODO: folder to watch
fs.mkdirSync(WATCH_DIR, { recursive: true });
function onChange(filename, eventType) {
// TODO: replace with your real action
console.log(`[${eventType}] ${filename}`);
}
fs.watch(WATCH_DIR, (eventType, filename) => {
if (filename) onChange(filename, eventType);
});
console.log(`Watching ${path.resolve(WATCH_DIR)}...`);
CLI Tool Starter
Argument parsing, verbose flag, and file output already wired up.
View skeleton script
import argparse
def main():
parser = argparse.ArgumentParser(description="TODO: describe your tool")
parser.add_argument("input", help="Input file or value")
parser.add_argument("-o", "--output", default=None, help="Output path")
parser.add_argument("-v", "--verbose", action="store_true")
args = parser.parse_args()
if args.verbose:
print(f"Processing: {args.input}")
result = process(args.input) # TODO: your real logic
if args.output:
with open(args.output, "w") as f:
f.write(result)
else:
print(result)
def process(value):
return value.upper() # TODO: replace
if __name__ == "__main__":
main()
#!/usr/bin/env node
const args = process.argv.slice(2);
// ── PARSE ─────────────────────────────────
const input = args[0];
const verbose = args.includes('-v') || args.includes('--verbose');
const outIndex = args.findIndex(a => a === '-o' || a === '--output');
const output = outIndex !== -1 ? args[outIndex + 1] : null;
if (!input) {
console.error('Usage: mytool <input> [-o output] [-v]');
process.exit(1);
}
if (verbose) console.error(`Processing: ${input}`);
const result = transform(input); // TODO: your real logic
if (output) {
require('fs').writeFileSync(output, result);
} else {
console.log(result);
}
function transform(value) {
return value.toUpperCase(); // TODO: replace
}
Scheduled Task Runner
Runs jobs on a recurring schedule without cron or a task scheduler service.
View skeleton script
import time
import schedule # pip install schedule
# ── JOBS ──────────────────────────────────
def job():
# TODO: replace with your real task
print("Running scheduled job...")
# ── SCHEDULE ──────────────────────────────
schedule.every(1).hours.do(job) # TODO: adjust interval
schedule.every().day.at("09:00").do(job) # TODO: or use a fixed time
if __name__ == "__main__":
print("Scheduler started, waiting for jobs...")
while True:
schedule.run_pending()
time.sleep(1)
const cron = require('node-cron'); // npm install node-cron
// ── JOBS ──────────────────────────────────
function job() {
// TODO: replace with your real task
console.log('Running scheduled job...');
}
// ── SCHEDULE ──────────────────────────────
cron.schedule('0 * * * *', job); // TODO: adjust — this runs hourly
cron.schedule('0 9 * * *', job); // TODO: or a fixed daily time
console.log('Scheduler started, waiting for jobs...');