LPI Web Dev Essentials study materials
LPI Web Dev Essentials study resources

LPI Web Dev Essentials common mistakes

LPI Web Dev Essentials · Understand the traps and how to avoid them.

Mistake 1

When asked to build a three-column layout, the candidate floats all columns or uses absolute positioning, then never clears the float, so the container collapses and the footer overlaps the columns.

Float-based layout is the first technique most tutorials teach, and candidates memorise float: left without learning that floated children are removed from normal flow, so the parent has zero height unless cleared. Absolute positioning seems easier because it 'just works' visually at one viewport width.

What to do instead

Use Flexbox or Grid for layout and reserve floats for wrapping text around images. If a float must be used, add overflow: auto or a clearfix (::after { content: ''; display: block; clear: both; }) to the container. Practise building a header/sidebar/main/footer shell with Grid template areas and a Flexbox nav bar until it is automatic.

Mistake 2

The candidate writes CSS selectors that target the wrong elements because of specificity conflicts, then patches the problem by scattering `!important` through the stylesheet.

Specificity (inline > id > class/attribute/pseudo-class > element) is easy to state but hard to apply under time pressure. When a rule 'does not work', the fastest perceived fix is !important rather than diagnosing which rule is winning.

What to do instead

Before adding a declaration, open DevTools, select the element, and read the Styles pane top-to-bottom: the first matching rule wins. Keep selectors flat (single class) and use BEM-style names to avoid collisions. Use !important only for utility overrides, and verify the cascade with a quick specificity calculation (count ids, then classes, then elements).

Mistake 3

The candidate uses non-semantic markup such as nested `div` elements with class names like `header` and `nav`, omits the `alt` attribute on decorative and content images alike, and skips form `label` elements.

Candidates focus on making the page look right and treat semantics as cosmetic. They also confuse alt="" (decorative, correctly empty) with a missing alt attribute (invalid and inaccessible), and they assume placeholder text in an input substitutes for a label.

What to do instead

Memorise the landmark elements (header, nav, main, article, section, aside, footer) and use them for structure. Give every content image a descriptive alt, decorative images alt="", and every form control a <label for="id"> or aria-label. Verify with a screen-reader-style check: tab through the page and confirm each control announces a name.

Mistake 4

The candidate writes HTML that renders acceptably in the browser but fails validation: unclosed `<p>` and `<li>` tags, duplicate `id` values, block elements nested inside `<a>` or `<p>`, and tables used purely for page layout.

Browsers auto-correct malformed markup, so the page looks fine and the candidate never sees the error. Duplicate ids are invisible until JavaScript's getElementById returns the wrong node, and table layout is a habit carried over from older tutorials.

What to do instead

Run every answer through the W3C Nu HTML Checker mentally: close all non-void elements, use each id once, keep <a> and <p> free of block-level children (or wrap properly), and use CSS Grid/Flexbox instead of layout tables. When scripting, prefer querySelector with class or data attributes so a duplicate id cannot silently break the code.

Mistake 5

The candidate uses `var` everywhere, then is surprised when a loop's callback prints the final index value, or when a variable declared inside a block leaks out and collides with another name.

var is function-scoped and hoisted, so closures created in a for loop all capture the same binding. Candidates who learned JavaScript before ES6 default to var and have not internalised block scoping.

What to do instead

Declare with const by default and let when reassignment is needed; both are block-scoped, so each loop iteration gets a fresh binding. Use var only when deliberately targeting function scope. Be able to explain hoisting: var declarations are hoisted and initialised to undefined, let/const are hoisted but stay in the temporal dead zone until initialised.

Mistake 6

The candidate treats `==` and `===` as interchangeable, so `'0' == false` is true, `null == undefined` is true, and comparisons in conditionals take unexpected branches.

Both operators look similar and loose equality 'usually works' with same-type values, so the difference is invisible until mixed types appear in an answer. Candidates also forget that NaN is not equal to itself under either operator.

What to do instead

Use === and !== for all comparisons unless you specifically need the null/undefined check (x == null is a deliberate idiom). Remember the coercions: 0 == '' is true, [] == false is true, NaN === NaN is false (use Number.isNaN). Read each conditional in your answer and ask what type each operand actually holds.

Mistake 7

The candidate calls an asynchronous function and immediately uses its result, so the value is `undefined` or a pending Promise; or they attach a `.then` but forget to return it, producing unhandled rejections and un-awaited work.

JavaScript does not block: fetch and fs.readFile return before data arrives. Candidates coming from synchronous languages expect line-by-line execution, and they forget that await is only legal inside an async function or at module top level.

What to do instead

Mark the containing function async, await every Promise you depend on, and wrap calls in try/catch (or .catch) to handle rejection. Return Promises from helper functions so callers can await them. When several independent operations run, use Promise.all and await the combined Promise rather than awaiting inside a loop.

Mistake 8

The candidate manipulates the DOM before it exists, so `document.querySelector` returns `null` and the script throws `Cannot read properties of null`, or they rebuild entire subtrees with `innerHTML` for every small update.

A <script> in <head> executes before the body is parsed. Separately, innerHTML is the most familiar mutation API, so candidates reach for it even though it destroys event listeners, re-parses markup, and opens an XSS hole when interpolating user input.

What to do instead

Place scripts at the end of <body> or add the defer attribute (and type="module" where appropriate) so the DOM is ready. Guard with DOMContentLoaded when needed. For updates, use textContent for text, createElement/append for nodes, and addEventListener instead of inline onclick attributes. Never interpolate untrusted strings into innerHTML.

Mistake 9

The candidate writes a Node HTTP server that never sets a status code or `Content-Type`, returns an object directly to `res.end`, or forgets to call `res.end` at all, leaving requests hanging.

The http module is low-level: it gives raw request and response streams with no framework defaults. Candidates assume Express-like conveniences (automatic JSON serialisation, default 200) carry over, and they do not realise a response stays open until ended.

What to do instead

Always call res.writeHead(status, { 'Content-Type': 'application/json' }) (or set res.statusCode and headers) before writing, serialise objects with JSON.stringify, and finish every path with res.end(...). Handle unknown routes with a 404 and errors with a 500. Test with curl -i to confirm status line and headers, and add a request listener that never falls through without responding.

Mistake 10

The candidate requires a relative module with the wrong path or extension, mixes CommonJS `require` with ES module `import` in the same file, or reads `process.argv` and `__dirname` as if they were browser globals.

Node resolves relative paths from the current file, requires ./ or ../ prefixes, and historically required .js extensions in ESM. require and import cannot be freely mixed, and __dirname does not exist in ES modules. Candidates also assume process.argv[0] is the first user argument.

What to do instead

Use require('./lib/util') for CommonJS or import x from './lib/util.js' in ESM (with "type": "module" in package.json), never both in one file. Remember process.argv[0] is the Node binary and [1] the script path, so user arguments start at index 2 (or use process.argv.slice(2)). In ESM, derive the directory with import.meta.url and fileURLToPath.