In-memory Rate Limiter

A rate limiter restricts how often a user or IP can perform an action.

Example:

Allow only 5 login attempts per minute.

Why rate limiting exists

Rate limiting protects APIs from:

Simple fixed-window limiter

const attempts = new Map()

function isAllowed(key, limit, windowMs) {
  const now = Date.now()
  const record = attempts.get(key)

  if (!record || now > record.resetAt) {
    attempts.set(key, {
      count: 1,
      resetAt: now + windowMs,
    })
    return true
  }

  if (record.count >= limit) {
    return false
  }

  record.count += 1
  return true
}

Usage:

if (!isAllowed('user-ip', 5, 60_000)) {
  return { error: 'Too many requests' }
}

Express middleware version

function createRateLimiter({ limit, windowMs }) {
  const attempts = new Map()

  return function rateLimiter(req, res, next) {
    const key = req.ip

    if (!isAllowedWithStore(attempts, key, limit, windowMs)) {
      return res.status(429).json({
        error: 'Too many requests',
      })
    }

    next()
  }
}

Usage:

app.post(
  '/auth/login',
  createRateLimiter({ limit: 5, windowMs: 60_000 }),
  loginController,
)

This is a realistic coding-round extension: first build the pure function, then wrap it as middleware.

Test cases

Test these cases:

Limitations

In-memory rate limiters reset when the server restarts.

They also do not work correctly across multiple server instances.

For production distributed systems, use Redis or another shared store.

Interview answer

A rate limiter controls how often a client can perform an action. A simple in-memory fixed-window limiter can store request counts in a Map with reset times. It works for one server process, but production multi-instance systems usually need a shared store like Redis.