This overview reflects widely shared professional practices as of May 2026; verify critical details against current official guidance where applicable.
Why Scheduling Matters: The Lunchbox Analogy
Imagine you're packing lunchboxes for a group of hungry coworkers. Each lunchbox (a pod) needs specific items: a sandwich, an apple, maybe a drink. You have several refrigerators (nodes) of different sizes. Some refrigerators are small, some are big, and each has limited shelf space. Your job is to place each lunchbox into a refrigerator such that everything fits, the lunchbox gets the food it needs, and no refrigerator is overloaded. If you put a lunchbox that needs a tall drink in a refrigerator with short shelves, the drink won't fit. If you cram too many lunchboxes into one fridge, the door won't close and food spoils. That's exactly what Kubernetes scheduling does: it places pods onto nodes so that resource requirements, constraints, and policies are satisfied.
For many beginners, scheduling is the most confusing part of Kubernetes. They see pods stuck in "Pending" state and don't know why. They add more nodes but still have problems. The lunchbox analogy makes it concrete: every pod has resource requests (the sandwich and apple), limits (maximum food it can eat), node selectors (only certain fridges), affinity rules (lunchboxes that should sit together), and taints/tolerations (fridges that repel certain lunchboxes). Understanding these concepts is the foundation of running reliable, efficient clusters.
Why does this matter in practice? Poor scheduling leads to wasted resources, uneven load, and application downtime. For example, if you run a memory-intensive database and schedule it on a node with little RAM, the pod gets killed. Or if you colocate CPU-heavy pods on the same node, they compete and slow down. The scheduler is designed to avoid these problems, but only if you configure it correctly. This article will demystify scheduling using the lunchbox analogy, step by step. You'll learn how the scheduler thinks, how to influence its decisions, and how to troubleshoot when things go wrong.
By the end, you'll see that scheduling isn't magic—it's just a smart packing algorithm. You'll be able to read scheduling logs, understand why a pod landed where it did, and tweak settings to match your workload. Let's open the lunchbox and see what's inside.
The Scheduler's Decision Process
The Kubernetes scheduler follows a two-step process: filtering and scoring. In the filtering step, it removes nodes that cannot run the pod. For example, if a pod requests 2 CPU cores and a node only has 1 free, that node is filtered out. Similarly, if the pod has a node selector requiring a GPU, nodes without GPUs are removed. The remaining nodes are "feasible." Then, in the scoring step, the scheduler assigns a score to each feasible node based on resource availability, pod spreading policies, and other criteria. The node with the highest score wins. This is like picking the best refrigerator that has enough space, is near the kitchen, and isn't already overcrowded. The scheduler uses a default algorithm that balances CPU and memory utilization, but you can customize it with priority functions and plugins.
Understanding this process helps you diagnose scheduling failures. If a pod stays pending, check which nodes passed filtering. Maybe no node meets the resource requests, or all nodes have a taint that the pod doesn't tolerate. You can use kubectl describe pod to see scheduling events. For example, a message like "0/3 nodes are available: 1 Insufficient memory, 2 node(s) didn't match pod affinity" tells you exactly why. This is the first step to fixing the issue.
Why the Analogy Works
The lunchbox analogy works because it maps every scheduling concept to a familiar object. Pods are lunchboxes: they hold containers (food items). Nodes are refrigerators: they have capacity (shelves) and labels (like "freezer" or "dairy section"). Resource requests are the amount of food each lunchbox needs. Limits are the maximum it can take. Node selectors are like "only put this lunchbox in a fridge with a freezer." Affinity rules say "these two lunchboxes should be on the same shelf." Taints are like a sign on a fridge saying "no dairy" and tolerations are the lunchbox's ability to ignore that sign. This mental model makes it easy to remember and reason about scheduling behavior. It also helps when learning advanced topics like pod topology spread constraints or custom schedulers. Once you internalize the lunchbox, the rest becomes intuitive.
How the Scheduler Picks a Node: Filtering and Scoring
The scheduler's job is to match each pod to a node. It does this in a loop: for every unscheduled pod, it gets the list of all nodes, then applies a series of predicates (filters) to eliminate unsuitable nodes. Common predicates include: PodFitsResources (check if node has enough CPU/memory), PodMatchNodeSelector (check node labels), PodToleratesNodeTaints, CheckNodeDiskPressure, and CheckNodePIDPressure. After filtering, the scheduler scores the remaining nodes using priority functions. The default priority functions include LeastRequestedPriority (favors nodes with more free resources), BalancedResourceAllocation (favors balanced CPU/memory usage), and ImageLocality (favors nodes that already have the container image). The node with the highest total score is chosen.
This two-phase design ensures that scheduling is both correct and efficient. Filtering guarantees that the pod can run on the node without violating constraints. Scoring optimizes for a desirable distribution of workloads. For example, if two nodes both have enough CPU, the scheduler picks the one with more free memory to avoid hotspots. This is similar to choosing a refrigerator that has both shelf space and a good location. The scheduler also considers pod affinity and anti-affinity during scoring, which can override resource-based decisions. For instance, if you have a pod that must run on the same node as another pod (affinity), the scheduler will give a high score to nodes that host that other pod, even if they have less free resources.
In practice, you can observe these decisions. Use kubectl get events --sort-by='.lastTimestamp' to see scheduling events. You'll see messages like "Successfully assigned default/my-pod to node-3" or "Failed to schedule pod, 0/5 nodes are available." For deeper insight, enable the scheduler's verbose logs or use tools like kubectl-scheduler-simulator. Understanding filtering and scoring helps you design clusters that schedule efficiently. For example, if you have many small pods, you might want to use a bin-packing policy (like MostRequestedPriority) to consolidate them into fewer nodes and save resources. If you have bursty workloads, you might prefer spreading them out. The default scheduler handles most cases well, but knowing how to tweak it gives you control.
One common mistake is assuming that the scheduler always balances load evenly. It doesn't—it balances based on the scoring function. If you want strict spreading, you need to use pod topology spread constraints. Another mistake is forgetting that the scheduler only considers resource requests, not actual usage. If your pods use more memory than they request, they can overcommit a node. Always set accurate requests and limits. The lunchbox analogy helps here: if you pack a lunchbox that says it needs one apple but actually eats three, the fridge runs out of apples. So set requests to what your app typically needs, and limits to a safe maximum.
Default Scheduling Flow
When a pod is created, it enters a queue. The scheduler watches the queue and for each pod, it runs the scheduling cycle: first it fetches the list of nodes, then it runs the filtering plugins, then the scoring plugins, then it picks the highest-scoring node and binds the pod to it. The binding is asynchronous—the kubelet on the chosen node then creates the containers. If the binding fails (e.g., node goes down), the scheduler retries. This cycle repeats until the pod is scheduled. You can extend this flow by writing custom scheduler plugins or using multiple schedulers for different workloads. For most users, the default scheduler is sufficient, but understanding the flow helps when debugging.
Controlling Pod Placement with Node Selectors and Affinity
Node selectors are the simplest way to influence scheduling. You add labels to nodes (e.g., disk=ssd, region=us-east) and then specify nodeSelector in your pod spec. The scheduler will only consider nodes with matching labels. This is like putting a sign on a lunchbox: "only put me in a fridge with a freezer compartment." Node selectors are great for coarse-grained placement, such as running GPU workloads only on GPU nodes, or keeping sensitive workloads on specific hardware. However, they are limited—they only support exact matches and cannot express "prefer this node but fall back to others." For that, you need node affinity.
Node affinity is a more expressive feature that lets you specify rules like "prefer nodes with label zone=us-east1" or "require nodes with label instance-type=large." There are two types: requiredDuringSchedulingIgnoredDuringExecution (hard requirement) and preferredDuringSchedulingIgnoredDuringExecution (soft preference). The scheduler uses these rules during filtering and scoring. For example, you can set a required affinity that the node must have gpu=true, and a preferred affinity that it should also have region=us-east. The scheduler will first filter out nodes without GPUs, then give higher scores to nodes in us-east. This gives you fine-grained control without being too restrictive.
Pod affinity and anti-affinity go a step further: they control placement relative to other pods. For instance, you might want two pods (like a frontend and a backend) to run on the same node to reduce latency (pod affinity). Or you might want replicas of the same service to run on different nodes for high availability (pod anti-affinity). Pod affinity uses the same syntax as node affinity but references pod labels instead of node labels. A common use case is to co-locate a cache pod with an application pod. With pod affinity, you can say "schedule this pod on a node that already runs a pod with label app=cache." The scheduler will then prefer nodes that have that cache pod.
However, pod affinity can degrade scheduling performance because it requires the scheduler to check pod placement across nodes. Use it sparingly. For most scenarios, node affinity and anti-affinity are sufficient. Also remember that pod anti-affinity can prevent all replicas from scheduling if there aren't enough nodes. For example, if you have 3 replicas and require they run on different nodes, but you only have 2 nodes, one replica will stay pending. Always plan your cluster size accordingly. The lunchbox analogy: pod affinity is like saying "put my lunchbox next to my friend's lunchbox in the same fridge." Anti-affinity is like saying "don't put two egg salad sandwiches in the same fridge."
Practical Example: Running a Web App and Database
Imagine you have a web app and a database. You want the web app to be on nodes with SSDs for fast storage, and you want the database to be on a different node from the web app for isolation. You also want both to be in the same region. You can label nodes: disk=ssd and region=us-east. For the web app, use nodeSelector: disk=ssd and a preferred node affinity for region=us-east. For the database, use nodeSelector: disk=ssd and a pod anti-affinity to avoid co-location with the web app. This ensures the database gets its own node, both use SSDs, and they stay in the same region. Without these rules, the scheduler might place them on the same node, causing resource contention and a single point of failure.
Taints and Tolerations: Repelling and Attracting Pods
Taints and tolerations work together to control which pods can run on which nodes. A taint is a mark on a node that repels pods unless the pod has a matching toleration. Think of a taint as a "no entry" sign on a refrigerator: "No dairy products allowed." A pod with a toleration for that taint is like a lunchbox that says "I'm allowed to ignore the 'no dairy' sign." Taints are often used to reserve nodes for special workloads, like dedicating a node to monitoring tools or to isolate GPU workloads. For example, you might taint a GPU node with nvidia.com/gpu=true:NoSchedule. Only pods with a corresponding toleration will be scheduled on that node. This prevents regular pods from consuming GPU resources.
Tolerations are specified in the pod spec. They have a key, value, effect, and optional tolerationSeconds. The effect can be NoSchedule (don't schedule new pods), PreferNoSchedule (soft version, try to avoid), or NoExecute (evict existing pods that don't tolerate). The most common effect is NoSchedule. For instance, if you want to run a monitoring daemonset on all nodes, you can give it tolerations for all taints. This ensures it runs everywhere, even on dedicated nodes. Taints and tolerations are powerful because they work in combination with node selectors and affinity. You can have a node with a taint that only allows pods with a specific label, and also use node affinity to further filter.
A common pattern is to use taints to separate critical workloads from best-effort ones. For example, taint production nodes with workload=prod:NoSchedule and give production pods a toleration. Then, if a development pod accidentally gets created without a toleration, it will never land on a production node. This provides a safety net. Another pattern is to use taints to mark nodes that are about to be drained for maintenance. The node controller automatically adds a taint node.kubernetes.io/unschedulable:NoSchedule when you cordon a node, and pods without tolerations will avoid it. However, you should not rely solely on taints for fine-grained placement—combine them with node affinity for best results.
One pitfall is forgetting to add tolerations to system pods. For instance, if you taint all nodes, then core DNS and other add-ons won't schedule unless they have tolerations. Most cluster installers add tolerations automatically, but if you manually taint nodes, check that critical pods can still run. Also, be careful with NoExecute taints: they can evict running pods, causing downtime. Use them only when you intend to force pods off a node, such as during node maintenance. The lunchbox analogy: a taint is a "no tomatoes" sign. A toleration is a lunchbox that says "I'm fine with tomatoes." If you put a "no tomatoes" sign on every fridge, only lunchboxes with the toleration can be placed. That's how you reserve nodes for specific teams or workloads.
Real-World Use Case: Dedicated GPU Nodes
Suppose you have a cluster with a few GPU nodes for machine learning jobs. You want to ensure that only ML pods use these nodes, and regular web servers stay away. You taint each GPU node with gpu=true:NoSchedule. Then, in your ML pod spec, you add a toleration: key: gpu, operator: Equal, value: true, effect: NoSchedule. Additionally, you might add a node selector for gpu=true to be extra safe. This combination guarantees that only pods that explicitly request GPUs will land on those nodes. Without the taint, a web pod could accidentally schedule on a GPU node, wasting expensive resources. This is a standard practice in production clusters.
Resource Requests and Limits: Packing the Lunchbox Right
Resource requests and limits are the most important configuration for reliable scheduling. They tell the scheduler how much CPU and memory a pod needs. Requests are the minimum guaranteed amount—the scheduler ensures the node has at least that much free resources before placing the pod. Limits are the maximum the pod can consume; if it exceeds limits, it may be throttled (CPU) or killed (memory). Think of requests as the size of the lunchbox: it needs to fit on the shelf. Limits are the maximum amount of food the lunchbox can hold, but if it tries to take more than that, the lid pops open and food spills (the pod gets OOMKilled).
Setting accurate requests is crucial for efficient cluster utilization. If you set requests too high, you waste resources because the scheduler reserves more than the pod actually uses. If you set them too low, the node may become overcommitted, leading to performance issues or pod evictions. A good practice is to monitor actual usage over time and set requests to the 95th percentile of historical usage. For limits, set them to a safe maximum that prevents runaway resource consumption. For example, a web server might request 100m CPU and 200Mi memory, with a limit of 500m CPU and 500Mi memory. This allows it to burst during traffic spikes but caps its impact.
Another important concept is Quality of Service (QoS) classes. Pods are classified as Guaranteed (requests equal limits), Burstable (requests less than limits), or BestEffort (no requests or limits). The scheduler and kubelet treat them differently during resource contention. Guaranteed pods are the least likely to be evicted, while BestEffort pods are the first to go. If you run critical workloads, set them as Guaranteed by setting requests equal to limits. For batch jobs, Burstable is fine. This classification is like priority seating on a bus: Guaranteed pods have reserved seats, Burstable can stand, and BestEffort must get off if the bus is full.
Common mistakes include forgetting to set requests at all (pods become BestEffort and can be evicted anytime), setting requests equal to limits but too high (waste), or setting limits without requests (the scheduler ignores limits for scheduling decisions, so nodes can become overcommitted). Always set both. Also note that the scheduler only uses requests for filtering, not actual usage. So if your pod uses 2 GB but only requests 512 MB, the node may become overloaded. Use monitoring tools like Metrics Server or Prometheus to track actual usage and adjust requests accordingly. The lunchbox analogy: if you tell the scheduler your lunchbox needs 1 apple (request), it reserves one apple's space. But if you actually eat 3 apples and the fridge only has 2, you'll starve (OOM). So be honest about your appetite.
How to Determine the Right Values
Start by running your application without limits and monitor its peak usage. Use kubectl top pod to see current usage. For a new service, start with conservative requests (e.g., 100m CPU, 200Mi memory) and limits at 2x the requests. Gradually adjust based on production data. For stateful databases, set requests close to expected steady-state usage and limits at 110% for headroom. Avoid setting limits too tight—they can cause unnecessary throttling. Tools like Vertical Pod Autoscaler (VPA) can recommend values automatically. However, always verify recommendations against your workload's behavior.
Common Scheduling Pitfalls and How to Avoid Them
Even experienced Kubernetes users fall into scheduling traps. One common pitfall is resource fragmentation. When pods of different sizes are scheduled, small pods can fill gaps on nodes, leaving large pods unable to fit anywhere. This is like packing a fridge with small lunchboxes that leave oddly shaped gaps too small for a big lunchbox. To avoid this, consider using a bin-packing scheduler policy (MostRequestedPriority) to consolidate pods onto fewer nodes, or use cluster autoscaling to add bigger nodes. Another pitfall is ignoring pod disruption budgets (PDBs). If you use pod anti-affinity but don't set a PDB, a node drain can evict all replicas at once, causing downtime. Always set PDBs for critical workloads to ensure a minimum number of replicas remain available.
Another mistake is overusing pod affinity. While it seems useful, pod affinity can create scheduling deadlocks. For example, if pod A requires to be co-located with pod B, and pod B requires to be co-located with pod A, they can't schedule unless both can fit on the same node. This is a chicken-and-egg problem. Use pod affinity sparingly and prefer node affinity or topology spread constraints. Also, beware of taint mismatches. If you taint a node but forget to add tolerations to your pods, they will stay pending. This often happens when you taint nodes manually after pods are already running—existing pods won't be evicted unless the taint effect is NoExecute, but new pods won't schedule. Always double-check taints and tolerations when troubleshooting pending pods.
A subtle issue is the interaction between node selectors and taints. If you use both, the scheduler must satisfy both. For instance, if a pod has a node selector for disk=ssd and the only SSD node has a taint that the pod doesn't tolerate, the pod will remain pending. This can be confusing because the node appears eligible by label but not by taint. Always check both conditions. Another pitfall is forgetting that the scheduler only sees resource requests, not limits. If you set a low request but a high limit, the node may become overcommitted. For example, a pod requests 100m CPU but limits 2 CPU. If you run 10 such pods on a single-core node, they request 1 CPU total (fine), but they could collectively use 20 CPU, causing severe throttling. Always set limits in proportion to requests.
Finally, cluster autoscaling can cause scheduling surprises. If you use a cluster autoscaler, it may add nodes when pods are pending. But if the new nodes don't match the pod's constraints (e.g., wrong zone, missing GPU), the pod will still be pending. The autoscaler only adds nodes that match the pending pod's requirements. So if you have a pod with a strict node affinity for a zone that doesn't have autoscaling enabled, it may never schedule. Ensure your autoscaling configuration covers all zones and instance types your pods might need. The lunchbox analogy: you have a fridge that can expand (autoscaler), but it only adds shelves that match the lunchbox's requirements. If the lunchbox needs a freezer shelf but the autoscaler only adds regular shelves, it won't help.
Debugging Pending Pods
When a pod is stuck in Pending, first describe it: kubectl describe pod <pod-name>. Look at the Events section. Common messages: "0/3 nodes are available: 1 Insufficient memory, 2 node(s) didn't match pod affinity/anti-affinity." This tells you exactly what's wrong. If it says "0/3 nodes are available: 3 node(s) had taints that the pod didn't tolerate," you need to add tolerations. If it says "0/3 nodes are available: 3 node(s) didn't match node selector," check your node labels. Use kubectl get nodes --show-labels to see available labels. If the issue is resource insufficiency, consider adding more nodes or reducing resource requests. Tools like kubectl-allocate can help simulate scheduling decisions. Remember, the scheduler logs are also helpful—enable them with --v=4 on the scheduler pod for verbose output.
Advanced Scheduling: Topology Spread and Custom Schedulers
Topology spread constraints allow you to control how pods are spread across failure domains like zones or nodes. This is crucial for high availability. For example, you can specify that all replicas of a service should be spread across at least three zones, with no more than one replica per zone. The syntax is a bit complex: you define a topologySpreadConstraints array with maxSkew, topologyKey, whenUnsatisfiable, and labelSelector. The maxSkew is the maximum allowed difference in the number of pods across topology domains. Setting maxSkew: 1 ensures even distribution. The whenUnsatisfiable can be DoNotSchedule (hard requirement) or ScheduleAnyway (soft preference).
For instance, if you have 3 zones and 6 replicas, with maxSkew: 1, the scheduler will try to put 2 replicas in each zone. If one zone has 3 nodes and another has 1, it will still aim for balance. This is like distributing lunchboxes across different refrigerators in different rooms to ensure if one fridge breaks, you still have lunch. Topology spread constraints are more flexible than pod anti-affinity because they work across arbitrary topology keys (zones, regions, nodes) and allow controlled skew. Use them for stateless workloads that need high availability. For stateful workloads, consider using StatefulSets with anti-affinity.
Custom schedulers are another advanced topic. Kubernetes allows you to run multiple schedulers in the same cluster. Each pod can specify a schedulerName to use a specific scheduler. This is useful for specialized workloads that need custom logic, such as GPU scheduling, latency optimization, or cost-based placement. You can write a custom scheduler using the Kubernetes scheduling framework, which provides plugins for filtering, scoring, and binding. However, for most teams, the default scheduler is sufficient. Writing a custom scheduler is a significant effort and should only be done if the default cannot meet your needs. Many organizations use the default scheduler with custom priority functions or webhooks (like the Scheduler Extender) instead of a full custom scheduler.
One practical advanced technique is using node conditions and taints for graceful node shutdown. When a node is about to be shut down (e.g., for maintenance), you can add a taint with NoExecute effect and tolerationSeconds to give pods time to gracefully migrate. This is part of the Node Lifecycle Controller. Combined with pod priority and preemption, you can ensure critical pods are moved first. The lunchbox analogy: if one fridge is about to break, you move the lunchboxes to other fridges, but you give priority to the lunchboxes with the most important food (high priority pods). Understanding these advanced features helps you build resilient, production-grade clusters.
When to Use Topology Spread vs. Pod Anti-Affinity
Use topology spread when you want even distribution across zones or regions, regardless of pod labels. Use pod anti-affinity when you want to avoid co-location of specific pods (e.g., same application). They can also be combined: use topology spread for zone-level distribution and pod anti-affinity for node-level dispersion. For example, you might spread replicas across zones with topology spread, and within each zone, use anti-affinity to ensure no two replicas land on the same node. This gives you both zone failure tolerance and node failure tolerance.
Mini-FAQ: Scheduling Questions Answered
Q: Why is my pod stuck in Pending even though there are free resources?
A: Check taints, node selectors, and affinity rules. The pod might have a node selector that no node matches, or all nodes have a taint the pod doesn't tolerate. Also verify that the pod's resource requests don't exceed any node's allocatable resources. Use kubectl describe pod to see events.
Q: Can I force a pod to run on a specific node?
A: Yes, use nodeName in the pod spec. This bypasses the scheduler entirely and directly assigns the pod to a node. However, this is not recommended because it breaks scheduling logic and can cause issues if the node fails. Use node affinity or taints instead.
Q: What's the difference between nodeSelector and node affinity?
A: nodeSelector is simpler—it only supports exact label matches. Node affinity supports complex expressions (In, NotIn, Exists, DoesNotExist) and can be required or preferred. Use nodeSelector for simple cases and node affinity for more control.
Q: How do I spread pods across nodes for high availability?
A: Use pod anti-affinity (preferred or required) with a topology key of kubernetes.io/hostname. Or use topology spread constraints with topologyKey: kubernetes.io/hostname and maxSkew: 1. The latter is more flexible.
Q: What happens if I set limits but not requests?
A: The scheduler will use the limits as requests (for CPU and memory). This can lead to over-reservation because limits are often higher than actual usage. Always set both separately.
Q: Can I change a pod's scheduling after it's running?
A: No, scheduling is immutable. To move a pod, you must delete and recreate it. For stateful workloads, use a StatefulSet with a Headless Service and update the pod template to trigger a rolling update.
Q: How does cluster autoscaler interact with scheduling?
A: The autoscaler adds nodes when pods are unschedulable due to resource shortages. It considers the pending pod's constraints (node selectors, affinity, etc.) when deciding which node type to add. If no node type matches, the pod remains pending.
Q: What is pod priority and preemption?
A: Pod priority allows you to assign importance to pods. When a high-priority pod cannot schedule, the scheduler may preempt (evict) lower-priority pods to free resources. Use this for critical workloads, but be aware it can cause disruption. Set priority carefully.
Q: My pods are scheduled but running slowly. Is that a scheduling issue?
A: Possibly. If nodes are overcommitted (actual usage exceeds requests), pods may be throttled. Check node resource usage with kubectl top nodes. Adjust requests and limits, or add more nodes. Scheduling only handles placement, not runtime performance.
Synthesis and Next Actions
You now have a solid understanding of Kubernetes scheduling through the lunchbox analogy. The key takeaways: pods are lunchboxes, nodes are refrigerators, the scheduler packs them efficiently using filtering and scoring. You learned how to control placement with node selectors, affinity, taints, and tolerations. You also learned the importance of accurate resource requests and limits, common pitfalls, and advanced features like topology spread constraints. Scheduling is not magic—it's a logical process you can influence and debug.
Your next steps: start by auditing your current cluster. Check if your pods have appropriate resource requests and limits. Use kubectl describe nodes to see resource allocation. Look for pods stuck in Pending and diagnose using the techniques above. Then, implement taints and tolerations to isolate critical workloads. For example, taint your production nodes and add tolerations to production pods. Next, review your pod distribution: are your replicas spread across nodes or zones? Use topology spread constraints or anti-affinity to improve resilience. Finally, consider using a cluster autoscaler if you have variable workloads.
Remember, scheduling is a journey. Start simple, monitor your cluster, and gradually apply more advanced features as needed. Don't overcomplicate—the default scheduler works well for most scenarios. The lunchbox analogy will help you reason about scheduling decisions and communicate them to your team. Now go pack those lunchboxes efficiently!
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!