diff --git a/package-lock.json b/package-lock.json
index 10a710e..d9a40d9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,9 +17,9 @@
         "dox": "^1.0.0",
         "eslint": "^8.50.0",
         "eslint-plugin-import": "^2.28.1",
-        "ramda": "^0.29.1",
+        "ramda": "^0.30.0",
         "rimraf": "^5.0.5",
-        "tsd": "^0.29.0",
+        "tsd": "^0.31.0",
         "typescript": "^5.2.2",
         "xyz": "^4.0.0"
       }
@@ -350,9 +350,9 @@
       "dev": true
     },
     "node_modules/@tsd/typescript": {
-      "version": "5.2.2",
-      "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.2.2.tgz",
-      "integrity": "sha512-VtjHPAKJqLJoHHKBDNofzvQB2+ZVxjXU/Gw6INAS9aINLQYVsxfzrQ2s84huCeYWZRTtrr7R0J7XgpZHjNwBCw==",
+      "version": "5.4.5",
+      "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.4.5.tgz",
+      "integrity": "sha512-saiCxzHRhUrRxQV2JhH580aQUZiKQUXI38FcAcikcfOomAil4G4lxT0RfrrKywoAYP/rqAdYXYmNRLppcd+hQQ==",
       "dev": true,
       "engines": {
         "node": ">=14.17"
@@ -3115,9 +3115,9 @@
       }
     },
     "node_modules/ramda": {
-      "version": "0.29.1",
-      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz",
-      "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==",
+      "version": "0.30.0",
+      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.0.tgz",
+      "integrity": "sha512-13Y0iMhIQuAm/wNGBL/9HEqIfRGmNmjKnTPlKWfA9f7dnDkr8d45wQ+S7+ZLh/Pq9PdcGxkqKUEA7ySu1QSd9Q==",
       "dev": true,
       "funding": {
         "type": "opencollective",
@@ -3689,12 +3689,12 @@
       }
     },
     "node_modules/tsd": {
-      "version": "0.29.0",
-      "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.29.0.tgz",
-      "integrity": "sha512-5B7jbTj+XLMg6rb9sXRBGwzv7h8KJlGOkTHxY63eWpZJiQ5vJbXEjL0u7JkIxwi5EsrRE1kRVUWmy6buK/ii8A==",
+      "version": "0.31.0",
+      "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.31.0.tgz",
+      "integrity": "sha512-yjBiQ5n8OMv/IZOuhDjBy0ZLCoJ7rky/RxRh5W4sJ0oNNCU/kf6s3puPAkGNi59PptDdkcpUm+RsKSdjR2YbNg==",
       "dev": true,
       "dependencies": {
-        "@tsd/typescript": "~5.2.2",
+        "@tsd/typescript": "~5.4.3",
         "eslint-formatter-pretty": "^4.1.0",
         "globby": "^11.0.1",
         "jest-diff": "^29.0.3",
@@ -4284,9 +4284,9 @@
       "dev": true
     },
     "@tsd/typescript": {
-      "version": "5.2.2",
-      "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.2.2.tgz",
-      "integrity": "sha512-VtjHPAKJqLJoHHKBDNofzvQB2+ZVxjXU/Gw6INAS9aINLQYVsxfzrQ2s84huCeYWZRTtrr7R0J7XgpZHjNwBCw==",
+      "version": "5.4.5",
+      "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.4.5.tgz",
+      "integrity": "sha512-saiCxzHRhUrRxQV2JhH580aQUZiKQUXI38FcAcikcfOomAil4G4lxT0RfrrKywoAYP/rqAdYXYmNRLppcd+hQQ==",
       "dev": true
     },
     "@types/eslint": {
@@ -6263,9 +6263,9 @@
       "dev": true
     },
     "ramda": {
-      "version": "0.29.1",
-      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz",
-      "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==",
+      "version": "0.30.0",
+      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.0.tgz",
+      "integrity": "sha512-13Y0iMhIQuAm/wNGBL/9HEqIfRGmNmjKnTPlKWfA9f7dnDkr8d45wQ+S7+ZLh/Pq9PdcGxkqKUEA7ySu1QSd9Q==",
       "dev": true
     },
     "react-is": {
@@ -6677,12 +6677,12 @@
       }
     },
     "tsd": {
-      "version": "0.29.0",
-      "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.29.0.tgz",
-      "integrity": "sha512-5B7jbTj+XLMg6rb9sXRBGwzv7h8KJlGOkTHxY63eWpZJiQ5vJbXEjL0u7JkIxwi5EsrRE1kRVUWmy6buK/ii8A==",
+      "version": "0.31.0",
+      "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.31.0.tgz",
+      "integrity": "sha512-yjBiQ5n8OMv/IZOuhDjBy0ZLCoJ7rky/RxRh5W4sJ0oNNCU/kf6s3puPAkGNi59PptDdkcpUm+RsKSdjR2YbNg==",
       "dev": true,
       "requires": {
-        "@tsd/typescript": "~5.2.2",
+        "@tsd/typescript": "~5.4.3",
         "eslint-formatter-pretty": "^4.1.0",
         "globby": "^11.0.1",
         "jest-diff": "^29.0.3",
diff --git a/package.json b/package.json
index a4909cf..809bb67 100644
--- a/package.json
+++ b/package.json
@@ -67,9 +67,9 @@
     "dox": "^1.0.0",
     "eslint": "^8.50.0",
     "eslint-plugin-import": "^2.28.1",
-    "ramda": "^0.29.1",
+    "ramda": "^0.30.0",
     "rimraf": "^5.0.5",
-    "tsd": "^0.29.0",
+    "tsd": "^0.31.0",
     "typescript": "^5.2.2",
     "xyz": "^4.0.0"
   }
diff --git a/test/head.test.ts b/test/head.test.ts
index 41e3670..5a50245 100644
--- a/test/head.test.ts
+++ b/test/head.test.ts
@@ -1,25 +1,62 @@
 import { expectType } from 'tsd';
 import { head } from '../es';
+import { isNotEmpty } from '../types/isNotEmpty';
 
+// strings always return `string | undefined`, can't determine "emptiness" like you can with arrays
+expectType<string | undefined>(head(''));
 // string always return string
-expectType<string>(head('abc'));
-// emptyString still returns type string. this is due to ramda's implementation `''.chartAt(0) => ''`
-expectType<string>(head(''));
+expectType<string | undefined>(head('abc'));
 
 // array literals will read the type of the first entry
 expectType<string>(head(['fi', 1, 'fum']));
-// but if the array is typed as an `Array<T> or T[]`, then return type will be `T`
-expectType<string | number | undefined>(head(['fi', 1, 'fum'] as Array<string | number>));
-// empty array literals return never
-expectType<never>(head([]));
-// but if it is typed, it will be `T | undefined`
+// empty array literals return undefined
+expectType<undefined>(head([]));
+
+// typed empty array will be `T | undefined`
 expectType<number | undefined>(head([] as number[]));
-// const tuples return the literal type of the first entry
+// as will a typed populated array
+expectType<number | undefined>(head([1, 2, 3] as number[]));
+
+// const tuples return the literal type of the last entry
+expectType<10>(head([10] as const));
 expectType<10>(head([10, 'ten'] as const));
 expectType<'10'>(head(['10', 10] as const));
-// typed tuples return the underlying type
-expectType<number>(head([10, 'ten'] as [number, string]));
-expectType<string>(head(['10', 10] as [string, number]));
+// typed tuples return the type of the last element
+expectType<boolean>(head([true] as [boolean]));
+expectType<number>(head([10, 'ten', true] as [number, string, boolean]));
+expectType<string>(head(['10', 10, false] as [string, number, boolean]));
+// typed empty tuple returns undefined, this is expected because there is no `T` here
+expectType<undefined>(head([] as []));
+
 // typed arrays return `T | undefined`
 expectType<number | string | undefined>(head([10, 'ten'] as Array<number | string>));
 expectType<string | number | undefined>(head(['10', 10] as Array<string | number>));
+expectType<number | string | undefined>(head([10, 'ten'] as ReadonlyArray<number | string>));
+expectType<string | number | undefined>(head(['10', 10] as ReadonlyArray<string | number>));
+
+// cross function testing with isNotEmpty
+// test the type narrowing
+const readonlyArr: readonly number[] = [];
+if (isNotEmpty(readonlyArr)) {
+  expectType<number>(head(readonlyArr));
+}
+
+const readonlyArr2: readonly number[] = [];
+if (!isNotEmpty(readonlyArr2)) {
+  // no-op
+} else {
+  expectType<number>(head(readonlyArr2));
+}
+
+
+const arr: number[] = [];
+if (isNotEmpty(arr)) {
+  expectType<number>(head(arr));
+}
+
+const arr2: number[] = [];
+if (!isNotEmpty(arr2)) {
+  // no-op
+} else {
+  expectType<number>(head(arr2));
+}
diff --git a/test/init.test.ts b/test/init.test.ts
new file mode 100644
index 0000000..7b118d2
--- /dev/null
+++ b/test/init.test.ts
@@ -0,0 +1,36 @@
+import { expectType } from 'tsd';
+
+// TODO: check this import to '../es' once this function actually exists in ramda
+import { isNotEmpty } from '../types/isNotEmpty';
+import { init } from '../es';
+
+// string always return string
+expectType<string>(init('abc'));
+// emptyString still returns type string. this is due to `''.chartAt(0) => ''`
+expectType<string>(init(''));
+
+// array literals will read the first type correctly
+expectType<[string, number]>(init(['fi', 1, 'fum']));
+// but if the array is typed as an `Array<T> or T[]`, then return type will be `T`
+expectType<Array<string | number>>(init(['fi', 1, 'fum'] as Array<string | number>));
+// empty array literals return never
+expectType<never[]>(init([]));
+// but if it is typed, it will be `number[]`
+expectType<number[]>(init([] as number[]));
+// single entry tuples return never, since they literally have no init
+expectType<[]>(init([10] as const));
+// tuples return the example type of the input tuple minus the first entry
+expectType<[10, '10']>(init([10, '10', 10] as const));
+expectType<['10', 10]>(init(['10', 10, '10'] as const));
+// typed arrays return the same type
+expectType<Array<number | string>>(init([10, 'ten'] as Array<number | string>));
+expectType<Array<string | number>>(init(['10', 10] as Array<string | number>));
+
+// works correctly with isNotEmpty
+const arr = [1, 2, 3, 4];
+
+expectType<number[]>(init(arr));
+
+if (isNotEmpty(arr)) {
+  expectType<number[]>(init(arr));
+}
diff --git a/test/isNotEmpty.test.ts b/test/isNotEmpty.test.ts
new file mode 100644
index 0000000..a78bbdb
--- /dev/null
+++ b/test/isNotEmpty.test.ts
@@ -0,0 +1,45 @@
+import { expectType } from 'tsd';
+
+// TODO: check this import to '../es' once this function actually exists in ramda
+import { isNotEmpty } from '../types/isNotEmpty';
+import { ReadonlyNonEmptyArray, NonEmptyArray } from '../es';
+
+
+// test the type narrowing
+const readonlyArr: readonly number[] = [];
+if (isNotEmpty(readonlyArr)) {
+  expectType<ReadonlyNonEmptyArray<number>>(readonlyArr);
+}
+
+const readonlyArr2: readonly number[] = [];
+if (!isNotEmpty(readonlyArr2)) {
+  // no-op
+} else {
+  expectType<ReadonlyNonEmptyArray<number>>(readonlyArr2);
+}
+
+
+const arr: number[] = [];
+if (isNotEmpty(arr)) {
+  expectType<NonEmptyArray<number>>(arr);
+}
+
+const arr2: number[] = [];
+if (!isNotEmpty(arr2)) {
+// no-op
+} else {
+  expectType<NonEmptyArray<number>>(arr2);
+}
+
+
+// tuples retain their type
+const tuple: [number, string] = [1, '1'];
+if (isNotEmpty(tuple)) {
+  expectType<[number, string]>(tuple);
+}
+
+// `as const` retain their type
+const tuple2 = [1, 2, 3] as const;
+if (isNotEmpty(tuple2)) {
+  expectType<readonly [1, 2, 3]>(tuple2);
+}
diff --git a/test/last.test.ts b/test/last.test.ts
new file mode 100644
index 0000000..1010f4c
--- /dev/null
+++ b/test/last.test.ts
@@ -0,0 +1,62 @@
+import { expectType } from 'tsd';
+import { last } from '../es';
+import { isNotEmpty } from '../types/isNotEmpty';
+
+// strings always return `string | undefined`, can't determine "emptiness" like you can with arrays
+expectType<string | undefined>(last(''));
+// string always return string
+expectType<string | undefined>(last('abc'));
+
+// array literals will read the type of the first entry
+expectType<string>(last(['fi', 1, 'fum']));
+// empty array literals return undefined
+expectType<undefined>(last([]));
+
+// typed empty array will be `T | undefined`
+expectType<number | undefined>(last([] as number[]));
+// as will a typed populated array
+expectType<number | undefined>(last([1, 2, 3] as number[]));
+
+// const tuples return the literal type of the last entry
+expectType<10>(last([10] as const));
+expectType<'ten'>(last([10, 'ten'] as const));
+expectType<10>(last(['10', 10] as const));
+// typed tuples return the type of the last element
+expectType<boolean>(last([true] as [boolean]));
+expectType<string>(last([true, 10, 'ten'] as [boolean, number, string]));
+expectType<number>(last([false, '10', 10] as [boolean, string, number]));
+// typed empty tuple returns undefined, this is expected because there is no `T` here
+expectType<undefined>(last([] as []));
+
+// typed arrays return `T | undefined`
+expectType<number | string | undefined>(last([10, 'ten'] as Array<number | string>));
+expectType<string | number | undefined>(last(['10', 10] as Array<string | number>));
+expectType<number | string | undefined>(last([10, 'ten'] as ReadonlyArray<number | string>));
+expectType<string | number | undefined>(last(['10', 10] as ReadonlyArray<string | number>));
+
+// cross function testing with isNotEmpty
+// test the type narrowing
+const readonlyArr: readonly number[] = [];
+if (isNotEmpty(readonlyArr)) {
+  expectType<number>(last(readonlyArr));
+}
+
+const readonlyArr2: readonly number[] = [];
+if (!isNotEmpty(readonlyArr2)) {
+  // no-op
+} else {
+  expectType<number>(last(readonlyArr2));
+}
+
+
+const arr: number[] = [];
+if (isNotEmpty(arr)) {
+  expectType<number>(last(arr));
+}
+
+const arr2: number[] = [];
+if (!isNotEmpty(arr2)) {
+  // no-op
+} else {
+  expectType<number>(last(arr2));
+}
diff --git a/test/tail.test.ts b/test/tail.test.ts
index 8a1e356..3113d42 100644
--- a/test/tail.test.ts
+++ b/test/tail.test.ts
@@ -1,4 +1,7 @@
 import { expectType } from 'tsd';
+
+// TODO: check this import to '../es' once this function actually exists in ramda
+import { isNotEmpty } from '../types/isNotEmpty';
 import { tail } from '../es';
 
 // string always return string
@@ -11,14 +14,23 @@ expectType<[number, string]>(tail(['fi', 1, 'fum']));
 // but if the array is typed as an `Array<T> or T[]`, then return type will be `T`
 expectType<Array<string | number>>(tail(['fi', 1, 'fum'] as Array<string | number>));
 // empty array literals return never
-expectType<never>(tail([]));
+expectType<never[]>(tail([]));
 // but if it is typed, it will be `number[]`
 expectType<number[]>(tail([] as number[]));
 // single entry tuples return never, since they literally have no tail
-expectType<never>(tail([10] as const));
+expectType<[]>(tail([10] as const));
 // tuples return the example type of the input tuple minus the first entry
 expectType<['10', 10]>(tail([10, '10', 10] as const));
 expectType<[10, '10']>(tail(['10', 10, '10'] as const));
 // typed arrays return the same type
 expectType<Array<number | string>>(tail([10, 'ten'] as Array<number | string>));
 expectType<Array<string | number>>(tail(['10', 10] as Array<string | number>));
+
+// works correctly with isNotEmpty
+const arr = [1, 2, 3, 4];
+
+expectType<number[]>(tail(arr));
+
+if (isNotEmpty(arr)) {
+  expectType<number[]>(tail(arr));
+}
diff --git a/types/head.d.ts b/types/head.d.ts
index 3bc1cf8..858e107 100644
--- a/types/head.d.ts
+++ b/types/head.d.ts
@@ -1,9 +1,9 @@
-// string
-export function head(str: string): string;
-// empty tuple - purposefully `never`. `head` should never work on tuple type with no length
-export function head(list: readonly []): never;
-// non-empty tuple
-export function head<T1, TRest>(list: readonly [T1, ...TRest[]]): T1;
-// arrays, because these could be empty, they return `T | undefined`
-// this is no different than the tuple form since `T[]` can be empty at runtime
+import { ReadonlyNonEmptyArray } from '../es';
+
+export function head(str: string): string | undefined;
+// non-empty tuple - Readonly here catches regular tuples too
+export function head<T>(list: readonly [T, ...any[]]): T;
+// non-empty arrays - Readonly here catches regular arrays too
+export function head<T>(list: ReadonlyNonEmptyArray<T>): T;
+// arrays, because these could be empty, they return `T | undefined
 export function head<T>(list: readonly T[]): T | undefined;
diff --git a/types/init.d.ts b/types/init.d.ts
index 6120967..96765f1 100644
--- a/types/init.d.ts
+++ b/types/init.d.ts
@@ -1,2 +1,11 @@
+import { ReadonlyNonEmptyArray, NonEmptyArray } from '../es';
+
+// string
 export function init(list: string): string;
-export function init<T>(list: readonly T[]): T[];
+// `infer Init` only works on types like `readonly [1, '2', 3]` where you will get back `['2', 3]`
+// else, if the type is `string[]`, you'll get back `string[]`
+export function init<T extends readonly [...any]>(list: T):
+T extends readonly [...infer Init, any] ? Init :
+  T extends ReadonlyNonEmptyArray<infer A> ? A[] :
+    T extends NonEmptyArray<infer A> ? A[] : T;
+
diff --git a/types/isNotEmpty.d.ts b/types/isNotEmpty.d.ts
new file mode 100644
index 0000000..e7f66a5
--- /dev/null
+++ b/types/isNotEmpty.d.ts
@@ -0,0 +1,6 @@
+import { NonEmptyArray, ReadonlyNonEmptyArray } from './util/tools';
+
+// array has to come first, because readonly T[] falls through
+export function isNotEmpty<T>(value: T[]): value is NonEmptyArray<T>;
+export function isNotEmpty<T>(value: readonly T[]): value is ReadonlyNonEmptyArray<T>;
+export function isNotEmpty(value: any): boolean;
diff --git a/types/last.d.ts b/types/last.d.ts
index 6b0036f..e3bbab2 100644
--- a/types/last.d.ts
+++ b/types/last.d.ts
@@ -1,3 +1,9 @@
-export function last(str: string): string;
-export function last(list: readonly []): undefined;
-export function last<T extends any>(list: readonly T[]): T | undefined;
+import { ReadonlyNonEmptyArray } from '../es';
+
+export function last(str: string): string | undefined;
+// non-empty tuple - Readonly here catches regular tuples too
+export function last<T>(list: readonly [...any[], T]): T;
+// non-empty arrays - Readonly here catches regular arrays too
+export function last<T>(list: ReadonlyNonEmptyArray<T>): T;
+// arrays, because these could be empty, they return `T | undefined
+export function last<T>(list: readonly T[]): T | undefined;
diff --git a/types/tail.d.ts b/types/tail.d.ts
index f15ae37..c7777a5 100644
--- a/types/tail.d.ts
+++ b/types/tail.d.ts
@@ -1,10 +1,5 @@
 // string
 export function tail(list: string): string;
-// empty tuple - purposefully `never, They literally have no tail
-export function tail(list: readonly []): never;
-// length=1 tuples also return `never`. They literally have no tail
-export function tail<T>(list: readonly [T]): never;
-// non-empty tuples and array
-// `infer Rest` only works on types like `readonly [1, '2', 3]` where you will get back `['2', 3]`
+// `infer Tail` only works on types like `readonly [1, '2', 3]` where you will get back `['2', 3]`
 // else, if the type is `string[]`, you'll get back `string[]`
-export function tail<T extends readonly [...any]>(tuple: T): T extends readonly [any, ...infer Rest] ? Rest : T;
+export function tail<T extends readonly [...any]>(list: T): T extends readonly [any, ...infer Tail] ? Tail : T;
diff --git a/types/util/tools.d.ts b/types/util/tools.d.ts
index 2dd4618..d73166e 100644
--- a/types/util/tools.d.ts
+++ b/types/util/tools.d.ts
@@ -508,3 +508,15 @@ export type WidenLiterals<T> =
  * <created by @harris-miller>
  */
 export type ElementOf<Type extends readonly any[]> = Type[number];
+
+/**
+ * A clever way to represent a non-empty array
+ * <created by @harris-miller>
+ */
+export type NonEmptyArray<T> = [T, ...T[]];
+
+/**
+ * A clever way to represent a readonly non-empty array
+ * <created by @harris-miller>
+ */
+export type ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];